def __init__(self,time_value=None,value=None,meta_data=None): '''@param time: <int|float> (epoch time), now if not provided @param value: <int|float> value for this data point 0 default @param param: <dict> meta data about this data point. ''' if isinstance(time_value,(int,float)): self.time = time_value else: self.time = time.time() self.time_int = int(self.time) if isinstance(value,(int,float)): self.value = value else: self.value = 0 if type(meta_data) == dict: self.meta_data = dictwrapper(meta_data) else: self.meta_data = dictwrapper({}) self._core = {"time":self.time, "time_int":self.time_int, "value":self.value, "meta_data":self.meta_data}
def __init__(self, time_value=None, value=None, meta_data=None): '''@param time: <int|float> (epoch time), now if not provided @param value: <int|float> value for this data point 0 default @param param: <dict> meta data about this data point. ''' if isinstance(time_value, (int, float)): self.time = time_value else: self.time = time.time() self.time_int = int(self.time) if isinstance(value, (int, float)): self.value = value else: self.value = 0 if type(meta_data) == dict: self.meta_data = dictwrapper(meta_data) else: self.meta_data = dictwrapper({}) self._core = { "time": self.time, "time_int": self.time_int, "value": self.value, "meta_data": self.meta_data }
def stopInstance(self, label): """Stops an Application Instances based on our models rules @param label: (string) - app instance label @return defer.Deferred - be prepared for Failures() """ stop = dictwrapper(self.SHUTDOWN_INFO) #configure our protocol if 'debug' not in self.stopProtoKwargs: self.stopProtoKwargs['debug'] = False if 'timeout' not in self.stopProtoKwargs: self.stopProtoKwargs['timeout'] = self.DEFAULT_TIMEOUT if 'logger' not in self.stopProtoKwargs: self.stopProtoKwargs['logger'] = self.log return command(stop.STOP_CMD, stop.STOP_ARGS, stop.STOP_ENV, stop.STOP_PATH, stop.STOP_USEPTY, stop.STOP_CHILDFD, self.stopProtocol, *self.stopProtoArgs, **self.stopProtoKwargs)
def startInstance(self, label): """Starts an Application Instances based on our models rules @param label: (string) - app instance label @return defer.Deferred - be prepared for Failures() """ start = dictwrapper(copy.deepcopy(self.STARTUP_INFO)) #configure our protocol if 'debug' not in self.startProtoKwargs: self.startProtoKwargs['debug'] = False if 'timeout' not in self.startProtoKwargs: self.startProtoKwargs['timeout'] = self.DEFAULT_TIMEOUT if 'logger' not in self.startProtoKwargs: self.startProtoKwargs['logger'] = self.log #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # !! READ THIS COMMENT BLOCK !! # # the callback result from your protocol should return a dictionary # with a KEY 'pid' included and 'pid' should be an integer otherwise # the injected callback immediately following 'command' will fail to # update your instance state. You have been warned. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! result = None try: thisInst = self.getInstance(label) TIME = str(time.time()) anchor = DIGEST_INIT() anchor.update(self.name) anchor.update(label) anchor.update(TIME) #add some randomness anchor = str(anchor.hexdigest()) #inject some env variables that we may want to retrieve later ENV = { 'DRONED_IDENTIFIER': anchor, 'DRONED_STARTTIME': TIME, 'DRONED_LABEL': label, 'DRONED_APPLICATION': self.name, 'DRONED_LOGDIR': config.LOG_DIR, } if thisInst.version: ENV['DRONED_VERSION'] = thisInst.version #add these vars to the start env of the contained application start.START_ENV.update(ENV) d = command(start.START_CMD, start.START_ARGS, start.START_ENV, start.START_PATH, start.START_USEPTY, start.START_CHILDFD, self.startProtocol, *self.startProtoArgs, **self.startProtoKwargs) wfd = defer.waitForDeferred(d) yield wfd result = wfd.getResult() #we probably might not know the pid yet #if allowed by config search for the instance after some delay pid = result.get('pid', 0) #just in case the protocol knows it if isinstance(self.SEARCH_DELAY, (int, float)) and not pid: d = defer.Deferred() self.reactor.callLater(self.SEARCH_DELAY, d.callback, None) wfd = defer.waitForDeferred(d) yield wfd wfd.getResult() #don't care about this result d = self.findProcesses() wfd = defer.waitForDeferred(d) yield wfd data = wfd.getResult() if data: data = data.pop(0)[1] #we are just going to take the first result.update(data) #we should have the pid captured thisInst.pid = int(result.get('pid', 0)) except: result = Failure() yield result
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### from kitt.interfaces import moduleProvides, IDroneDService moduleProvides(IDroneDService) #requirement from kitt.util import dictwrapper import config SERVICENAME = 'janitizer' #default configuration cleanup logs older than 7 days SERVICECONFIG = dictwrapper({ 'JANITIZE': { config.LOG_DIR: [ ('.*.log.\d+.*', int(7 * len(config.AUTOSTART_SERVICES))), ], } }) import os, re, time from twisted.application.service import Service from twisted.internet import defer, task from droned.logging import logWithContext from kitt.decorators import synchronizedDeferred, deferredAsThread import copy __doc__ = """ config [JANITOR_DICT, AUTOSTART_SERVICES] This service when properly configured will keep the filesystem cleaned up when running.
from twisted.python.failure import Failure from twisted.internet import task, defer from twisted.application.service import Service from droned.logging import logWithContext, err from kitt.proc import listProcesses, isRunning from kitt.util import crashReport, dictwrapper from kitt.decorators import deferredAsThread, debugCall import config import sys #api requirement SERVICENAME = 'application' #this will be updated by the service loader and is an API requirement SERVICECONFIG = dictwrapper({ 'initial_delay': 1.0, #number of seconds to wait on start before scanning 'recover_interval': 10.0, #number of seconds to wait in between crash searches 'recovery_period': 60 #number of seconds between recovery attempts }) log = logWithContext(type=SERVICENAME) from droned.applications import pluginFactory pluginFactory.loadAppPlugins() #get all the app plugins ready to be used class ApplicationLoader(Service): scanning = defer.succeed(None) tracking = set() first_run = False def _first_scan(self):
# distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### from kitt.interfaces import moduleProvides, IDroneDService moduleProvides(IDroneDService) #requirement from kitt.util import dictwrapper import os SERVICENAME = 'drone' #override in romeo SERVICECONFIG = dictwrapper({ 'DRONED_SERVER_TIMEOUT': int(60 * 60 * 12), 'DRONED_WEBROOT': os.path.join(os.path.sep, 'var','lib','droned','WEB_ROOT'), 'DRONED_PORT': 5500, 'DRONED_PRIME_TTL': 120, }) from twisted.python.failure import Failure from twisted.application import internet from twisted.web import server, static, resource from twisted.internet import reactor, defer import config from kitt.util import unpackify from kitt.decorators import deferredAsThread from kitt import blaster from droned.logging import logWithContext from droned.entity import Entity from droned.clients import cancelTask
from twisted.internet import task, defer, reactor from twisted.application.service import Service from droned.logging import logWithContext, err from kitt.proc import listProcesses, isRunning from kitt.util import crashReport, dictwrapper from kitt.decorators import deferredAsThread, debugCall import config import sys #api requirement SERVICENAME = 'application' #this will be updated by the service loader and is an API requirement SERVICECONFIG = dictwrapper({ 'initial_delay': 1.0, #number of seconds to wait on start before scanning 'assimilate_interval': 60.0, #number of seconds to wait between assimilations 'recover_interval': 10.0, #number of seconds to wait in between crash searches 'recovery_period': 60 #number of seconds between recovery attempts }) log = logWithContext(type=SERVICENAME) from droned.applications import pluginFactory pluginFactory.loadAppPlugins() #get all the app plugins ready to be used class ApplicationLoader(Service): scanning = defer.succeed(None) assimilating = defer.succeed(None) tracking = set() def startService(self): """Start All AppManager Services"""
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### from kitt.interfaces import moduleProvides, IDroneDService moduleProvides(IDroneDService) #requirement from kitt.util import dictwrapper import config SERVICENAME = 'janitizer' #default configuration cleanup logs older than 7 days SERVICECONFIG = dictwrapper({ 'JANITIZE': { config.LOG_DIR: [ ('.*.log.\d+.*', int(7*len(config.AUTOSTART_SERVICES))), ], } }) import os, re, time from twisted.application.service import Service from twisted.internet import defer, task from droned.logging import logWithContext from kitt.decorators import synchronizedDeferred, deferredAsThread import copy __doc__ = """ config [JANITOR_DICT, AUTOSTART_SERVICES] This service when properly configured will keep the filesystem cleaned up when running.
def startInstance(self, label): """Starts an Application Instances based on our models rules @param label: (string) - app instance label @return defer.Deferred - be prepared for Failures() """ start = dictwrapper(copy.deepcopy(self.STARTUP_INFO)) #configure our protocol if 'debug' not in self.startProtoKwargs: self.startProtoKwargs['debug'] = False if 'timeout' not in self.startProtoKwargs: self.startProtoKwargs['timeout'] = self.DEFAULT_TIMEOUT if 'logger' not in self.startProtoKwargs: self.startProtoKwargs['logger'] = self.log #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # !! READ THIS COMMENT BLOCK !! # # the callback result from your protocol should return a dictionary # with a KEY 'pid' included and 'pid' should be an integer otherwise # the injected callback immediately following 'command' will fail to # update your instance state. You have been warned. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! result = None try: thisInst = self.getInstance(label) TIME = str(time.time()) anchor = DIGEST_INIT() anchor.update(self.name) anchor.update(label) anchor.update(TIME) #add some randomness anchor = str(anchor.hexdigest()) #inject some env variables that we may want to retrieve later ENV = { 'DRONED_IDENTIFIER': anchor, 'DRONED_STARTTIME': TIME, 'DRONED_LABEL': label, 'DRONED_APPLICATION': self.name, 'DRONED_LOGDIR': config.LOG_DIR, } if thisInst.version: ENV['DRONED_VERSION'] = thisInst.version #add these vars to the start env of the contained application start.START_ENV.update(ENV) d = command(start.START_CMD, start.START_ARGS, start.START_ENV, start.START_PATH, start.START_USEPTY, start.START_CHILDFD, self.startProtocol, *self.startProtoArgs, **self.startProtoKwargs) wfd = defer.waitForDeferred(d) yield wfd result = wfd.getResult() #we probably might not know the pid yet #if allowed by config search for the instance after some delay pid = result.get('pid', 0) #just in case the protocol knows it if isinstance(self.SEARCH_DELAY, (int, float)) and not pid: d = defer.Deferred() self.reactor.callLater(self.SEARCH_DELAY, d.callback, None) wfd = defer.waitForDeferred(d) yield wfd wfd.getResult() #don't care about this result d = self.findProcesses() wfd = defer.waitForDeferred(d) yield wfd data = wfd.getResult() if data: data = data.pop(0)[1] #we are just going to take the first result.update(data) #we should have the pid captured #this is the proper way to notify the AppInstance that we are running thisInst.pid = int(result.get('pid', 0)) except: result = Failure() yield result
# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### from kitt.interfaces import moduleProvides, IDroneDService moduleProvides(IDroneDService) #requirement from kitt.util import dictwrapper SERVICENAME = 'journal' SERVICECONFIG = dictwrapper({ 'JOURNAL_RETENTION': 60, 'JOURNAL_DIR': '/var/lib/droned/journal', 'JOURNAL_FREQUENCY': 60, }) from twisted.python.log import msg from twisted.python.failure import Failure from twisted.internet import defer, threads, task from twisted.application.service import Service from droned.entity import Entity from droned.logging import logWithContext, err from droned.models.event import Event import signal import config import time import os import gc
############################################################################### from kitt.interfaces import moduleProvides, IDroneDService moduleProvides(IDroneDService) #requirement from kitt.util import dictwrapper import os SERVICENAME = 'drone' #override in romeo SERVICECONFIG = dictwrapper({ 'DRONED_SERVER_TIMEOUT': int(60 * 60 * 12), 'DRONED_WEBROOT': os.path.join(os.path.sep, 'var', 'lib', 'droned', 'WEB_ROOT'), 'DRONED_PORT': 5500, 'DRONED_PRIME_TTL': 120, }) from twisted.python.failure import Failure from twisted.application import internet from twisted.web import server, static, resource from twisted.internet import defer, task import config from kitt.util import unpackify from kitt.decorators import deferredAsThread from kitt import blaster from droned.logging import logWithContext
class JabberClient(object): implements(IDroneDService) #requirement xmlstream = None connected = property(lambda self: self.xmlstream is not None) parentService = None #interface required attribute service = None #interface required attribute SERVICENAME = 'jabber' #interface required attribute #this should be overrode in romeo SERVICECONFIG = dictwrapper({ 'JABBER_CHAT_NICK': config.HOSTNAME, 'JABBER_USERNAME': '******', 'JABBER_PASSWORD': '******', 'JABBER_SERVER': 'jabber.example.net', 'JABBER_CHAT_SERVICE': 'conference.jabber.example.net', 'JABBER_PORT': 5222, 'JABBER_RESOURCE': config.HOSTNAME, 'JABBER_BROADCAST_INTERVAL': 300, 'JABBER_VALIDATE_XML': True, 'JABBER_TRUST_ROOM': False, 'DEPUTY': '*****@*****.**', 'CONVERSATION_RESPONSE_PERIOD': 180, 'JABBER_JOIN_CHATROOM': False, 'JABBER_TEAM_ROSTER': os.path.join(config.DRONED_HOMEDIR, 'teams'), }) #interface required attribute def running(self): """interface requirement""" return bool(self.service) and self.service.running def install(self, _parentService): """interface requirement""" self.parentService = _parentService user = decrypt(self.SERVICECONFIG.JABBER_USERNAME) server = self.SERVICECONFIG.JABBER_SERVER resource = self.SERVICECONFIG.JABBER_RESOURCE self.jid = JID("%(user)s@%(server)s/%(resource)s" % locals()) self.broadcastTask = LoopingCall(self.broadcastPresence) self.sendQueue = [] self.authenticated = False #load all jabber responders, after configuration import droned.responders droned.responders.loadAll() def start(self): """interface requirement""" if self.running(): return self.factory = XMPPClientFactory( self.jid, decrypt(self.SERVICECONFIG.JABBER_PASSWORD)) self.factory.addBootstrap(STREAM_CONNECTED_EVENT, self.connectionMade) self.factory.addBootstrap(STREAM_END_EVENT, self.connectionLost) self.factory.addBootstrap(STREAM_AUTHD_EVENT, self.connectionAuthenticated) self.factory.addBootstrap(STREAM_ERROR_EVENT, self.receivedError) self.factory.addBootstrap(INIT_FAILED_EVENT, self.initFailed) self.service = TCPClient(self.SERVICECONFIG.JABBER_SERVER, self.SERVICECONFIG.JABBER_PORT, self.factory) self.service.setServiceParent(self.parentService) #build/rebuild jabber teams if not os.path.exists(self.SERVICECONFIG.JABBER_TEAM_ROSTER): try: os.makedirs(self.SERVICECONFIG.JABBER_TEAM_ROSTER) except: log('Cannot load team rosters because %s does not exits' % \ self.SERVICECONFIG.JABBER_TEAM_ROSTER) return for name in os.listdir(self.SERVICECONFIG.JABBER_TEAM_ROSTER): f = (self.SERVICECONFIG.JABBER_TEAM_ROSTER, name) if os.path.isfile('%s/%s' % f): Team(name) #preload team rosters def stop(self): """interface requirement""" if self.service: self.factory.stopTrying() self.factory.stopFactory() self.service.disownServiceParent() self.service.stopService() self.service = None def connectionMade(self, xmlstream): log('connection made') self.xmlstream = xmlstream def connectionLost(self, xmlstream): log('connection lost') self.authenticated = False if self.broadcastTask.running: self.broadcastTask.stop() if self.connected: Event('jabber-offline').fire() self.xmlstream = None def connectionAuthenticated(self, xmlstream): log('connection authenticated') self.authenticated = True if not self.broadcastTask.running: self.broadcastTask.start( self.SERVICECONFIG.JABBER_BROADCAST_INTERVAL) xmlstream.addObserver('/message', self.receivedMessage) xmlstream.addObserver('/presence', self.receivedPresence) xmlstream.addObserver('/iq', self.receivedIQ) xmlstream.addObserver('/error', self.receivedError) Event('jabber-online').fire() while self.sendQueue: self.xmlstream.send(self.sendQueue.pop(0)) def broadcastPresence(self): presence = Element(('jabber:client', 'presence')) #log('sending presence broadcast') self.xmlstream.send(presence) def sendMessage(self, to, body, useHTML=True, groupChat=False): message = Element(('jabber:client', 'message')) message['to'] = to message['type'] = (groupChat and 'groupchat') or 'chat' message.addElement('body', None, body) if useHTML: html = message.addElement('html', 'http://jabber.org/protocol/xhtml-im') htmlBody = html.addElement('body', 'http://www.w3.org/1999/xhtml') htmlBody.addRawXml(unicode(body)) if self.SERVICECONFIG.JABBER_VALIDATE_XML: validateXml(html.toXml()) #safeXml = filter(lambda char: ord(char) < 128, message.toXml()) #log('sending message: %s' % safeXml) log('sending message to %s: %s' % (to, body)) if self.authenticated: self.xmlstream.send(message) else: log("not connected, queueing message", warning=True) self.sendQueue.append(message) def requestAuthorization(self, to): request = Element((None, 'iq')) request['type'] = 'set' request['id'] = 'auth-request:%s' % to query = Element((None, 'query')) query['xmlns'] = 'jabber:iq:roster' item = Element((None, 'item')) item['jid'] = to item['name'] = to.split('@')[0] query.addChild(item) request.addChild(query) log('sending auth request: %s' % request.toXml()) self.xmlstream.send(request) def joinChatRoom(self, room): presence = Element((None, 'presence')) presence['from'] = self.jid.userhost() jid = '%s@%s/%s' % (room, self.SERVICECONFIG.JABBER_CHAT_SERVICE, self.SERVICECONFIG.JABBER_CHAT_NICK) presence['to'] = jid x = Element(('http://jabber.org/protocol/muc', 'x')) history = Element((None, 'history')) history['maxchars'] = '0' x.addChild(history) presence.addChild(x) log('sending join: %s' % presence.toXml()) self.xmlstream.send(presence) def leaveChatRoom(self, jid): if '/' not in jid: jid += '/' + self.SERVICECONFIG.JABBER_CHAT_NICK presence = Element((None, 'presence')) presence['from'] = self.jid.userhost() presence['to'] = jid presence['type'] = 'unavailable' log('sending leave: %s' % presence.toXml()) self.xmlstream.send(presence) def receivedMessage(self, e): # Extract the body of the message try: message = str([c for c in e.children if c.name == 'body'][0]) except: log('discarding invalid message (has no body!): %s' % e.toXml()) return # Discard delayed messages delays = [x for x in e.children if x.name == 'delay'] stamps = [ x for x in e.children \ if x.name == 'x' and \ x.compareAttribute('xmlns','jabber:x:delay') and \ x.hasAttribute('stamp') ] #stampstring = str( stamps[0].getAttribute('stamp') ) #timestamp = time.mktime( time.strptime(stampstring, "%Y%m%dT%H:%M:%S") ) if delays or stamps: log('discarding delayed message: %s' % e.toXml()) return # Route message to the right Conversation or ChatRoom entity if e.getAttribute('type') == 'chat': buddy = str(e['from'].split('/')[0]) if not Conversation.exists(buddy): self.requestAuthorization(buddy) log('received message from %s: %s' % (buddy.split('@')[0], message)) Conversation(buddy).hear(message) elif e.getAttribute('type') == 'groupchat': room = e['from'].split('@')[0] log('received message [chatroom=%s]: %s' % (room, message)) ChatRoom(room).hear(message) else: log('received message of unknown type: %s' % e.toXml(), error=True) def receivedPresence(self, e): log('received presence: %s' % e.toXml()) if e.getAttribute('type') == 'subscribe': log('received authorization request from %s' % e['from']) response = Element(('', 'presence')) response['to'] = e['from'] response['type'] = 'subscribed' log('sending auth response: %s' % response.toXml()) self.xmlstream.send(response) buddy = str(e['from']) if not Conversation.exists(buddy): self.requestAuthorization(buddy) elif e.getAttribute('type') == 'unavailable': #fix for openfire jabber server randomly kicking clients out and prevent kicks CHAT = '@%s/%s' % (self.SERVICECONFIG.JABBER_CHAT_SERVICE, self.SERVICECONFIG.JABBER_CHAT_NICK) if e['to'] == self.jid.full() and e['from'].endswith(CHAT) and \ "status code='307'" in e.toXml(): try: log('%s has kicked me' % (e['from'], )) self.joinChatRoom(e['from'].split(CHAT)[0]) log('successfully rejoined room') except: err('Failed to recover from /kick') #elif any(1 for c in e.children if c.name == 'x'): #TODO detect buddies that go offline # if we have a Conversation then unsubscribe .notify from all events def receivedIQ(self, e): log('received iq: %s' % e.toXml()) def receivedError(self, f): log('received error: %s' % str(f)) def initFailed(self, failure): log('Failed to initialize jabber connection:\n%s' % failure.getTraceback()) self.stop() if failure.check(SASLAuthError): log('Will attempt to reconnect in 15 seconds...') config.reactor.callLater(15, self.start)
# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Jun 11, 2011: # @author: cbrinley # module name: systemstats ############################################################################### #api requirements from kitt.util import dictwrapper SERVICENAME = 'systemstats' SERVICECONFIG = dictwrapper({}) __doc__ = """ !!! NOTE !!!! This service does a bunch of small blocking IO operations. in most cases this will not be a problem at all however if you are running a lot higher frequency services or services that have sub-second requirements for other services you may notice interference from this service. This will be fixed in a future release. For the majority of operations oriented tasks this is not a big deal. This service will produce system stats at the assigned poll interval. CPU,Memory,Disk,network IO, Process specific stats are available. The below configuration as shown would be included for an individual server. It is also possible to put this as an include or via a label "&" sytnax. Minimum required config:
__author__ = "Justin Venus <*****@*****.**>" __doc__ = """A Service to provide romeo config over droned's command port""" from kitt.interfaces import moduleProvides, IDroneDService from kitt.util import dictwrapper, getException from twisted.web.resource import Resource from twisted.web.server import NOT_DONE_YET from twisted.web.error import NoResource from twisted.python.failure import Failure from droned.models.event import Event import romeo moduleProvides(IDroneDService) parentService = None service = None SERVICENAME = 'remote_config' SERVICECONFIG = dictwrapper({}) dependant_service = 'drone' #output formatters try: import simplejson as json except ImportError: try: import json except: json = None try: import cPickle as pickle except ImportError: import pickle from yaml import dump as yaml_dumper try: from yaml import CLoader as Loader, CDumper as Dumper except ImportError: from yaml import Loader, Dumper
# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### from kitt.interfaces import moduleProvides, IDroneDService moduleProvides(IDroneDService) # requirement from kitt.util import dictwrapper SERVICENAME = "journal" SERVICECONFIG = dictwrapper( {"JOURNAL_RETENTION": 60, "JOURNAL_DIR": "/var/lib/droned/journal", "JOURNAL_FREQUENCY": 60} ) from twisted.python.log import msg from twisted.python.failure import Failure from twisted.internet import defer, threads, task, reactor from twisted.application.service import Service from droned.entity import Entity from droned.logging import logWithContext, err from droned.models.event import Event import signal import config import time import os import gc