merging MyIro_deamon
authorSandro Knauß <knauss@netzguerilla.net>
Wed, 06 Oct 2010 04:42:23 +0200
changeset 45 4bde195af39f
parent 44 e20909e61588 (current diff)
parent 43 72c472c87460 (diff)
child 47 a0eac136eb20
merging MyIro_deamon
iro/anbieter/test.py
iro/test.py
--- a/iro/anbieter/anbieter.py	Mon Feb 08 01:35:19 2010 +0100
+++ b/iro/anbieter/anbieter.py	Wed Oct 06 04:42:23 2010 +0200
@@ -21,14 +21,3 @@
         pass
     def sendMail(self,mail,recipients):
         pass
-
-
-
-	
-
-
-
-
-
-
-
--- a/iro/anbieter/test.py	Mon Feb 08 01:35:19 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#This program is free software; you can redistribute it and/or modify it under the terms
-#of the GNU General Public License as published by the Free Software Foundation;
-#either version 3 of the License, or any later version.
-#This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-
-#You should have received a copy of the GNU General Public License
-#along with this program; if not, see <http://www.gnu.org/licenses/>.
-
-class anbieter:
-    default_conf = ''    # override this
-
-import unittest
-import telnumber as tn
-
-class TestTelnumber(unittest.TestCase):
-    def equalNumber(self, tel1, tel2):
-        self.assertEqual(tel1.number, tel2.number)
-        self.assertEqual(tel1.land, tel2.land)
-        
-    def testWrongNumber(self):
-        telnum=tn.telnumber()
-        self.assertRaises(tn.NotATelNumber, telnum.createNumber,  "hallo")
-        self.assertRaises(tn.NotATelNumber, telnum.createNumber,  "0?242")
-        
-   
-    def testNumber(self):
-        telnum=tn.telnumber("0551-28293640")
-        telnum2=tn.telnumber("+49551/28293640")
-        telnum3=tn.telnumber("00495512829364-0")
-        telnum4=tn.telnumber("+49(0)551-28293640")
-        
-        self.assertEqual(telnum.land, "49")
-        self.assertEqual(telnum.number, "55128293640")
-        
-        self.equalNumber(telnum, telnum2)
-        self.equalNumber(telnum, telnum3)
-        self.equalNumber(telnum, telnum4)
-
-    def testEqual(self):
-        telnum=tn.telnumber("0551-28293640")
-        telnum2=tn.telnumber("+49551/28293640")
-        li=[]
-        self.assertEqual(telnum == telnum2, True)
-        self.assertEqual(telnum <> telnum2, False)
-        self.assertEqual(telnum,  telnum2)
-        self.assertEqual(telnum in li,False)
-        li.append(telnum)
-        self.assertEqual(telnum in li,True)
-        self.assertEqual(telnum in li,True)
-
-if __name__ == "__main__":
-    unittest.main()  
-	
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/anbieter/tests/testTelnumber.py	Wed Oct 06 04:42:23 2010 +0200
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
+
+#This program is free software; you can redistribute it and/or modify it under the terms
+#of the GNU General Public License as published by the Free Software Foundation;
+#either version 3 of the License, or any later version.
+#This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#See the GNU General Public License for more details.
+
+#You should have received a copy of the GNU General Public License
+#along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+class anbieter:
+    default_conf = ''    # override this
+
+import unittest
+import iro.anbieter.telnumber as tn
+
+class TestTelnumber(unittest.TestCase):
+    def equalNumber(self, tel1, tel2):
+        self.assertEqual(tel1.number, tel2.number)
+        self.assertEqual(tel1.land, tel2.land)
+        
+    def testWrongNumber(self):
+        telnum=tn.telnumber()
+        self.assertRaises(tn.NotATelNumber, telnum.createNumber,  "hallo")
+        self.assertRaises(tn.NotATelNumber, telnum.createNumber,  "0?242")
+        
+   
+    def testNumber(self):
+        telnum=tn.telnumber("0551-28293640")
+        telnum2=tn.telnumber("+49551/28293640")
+        telnum3=tn.telnumber("00495512829364-0")
+        telnum4=tn.telnumber("+49(0)551-28293640")
+        
+        self.assertEqual(telnum.land, "49")
+        self.assertEqual(telnum.number, "55128293640")
+        
+        self.equalNumber(telnum, telnum2)
+        self.equalNumber(telnum, telnum3)
+        self.equalNumber(telnum, telnum4)
+
+    def testEqual(self):
+        telnum=tn.telnumber("0551-28293640")
+        telnum2=tn.telnumber("+49551/28293640")
+        li=[]
+        self.assertEqual(telnum == telnum2, True)
+        self.assertEqual(telnum <> telnum2, False)
+        self.assertEqual(telnum,  telnum2)
+        self.assertEqual(telnum in li,False)
+        li.append(telnum)
+        self.assertEqual(telnum in li,True)
+        self.assertEqual(telnum in li,True)
+
+if __name__ == "__main__":
+    unittest.main()  
+	
+
--- a/iro/job.py	Mon Feb 08 01:35:19 2010 +0100
+++ b/iro/job.py	Wed Oct 06 04:42:23 2010 +0200
@@ -32,11 +32,14 @@
     def stop(self):
         self.status = "stopped"
         
-    def getStatus(self,detailed):
+    def getStatus(self,detailed=False):
         if detailed and self.status == "started" or self.status == "sended":
             return self.status, self.dStatus
-        return self.status, {}
+        return self.status
     
+    def setStatus(self,status):
+        self.status=status
+
     def getName(self):
         return self.name
         
--- a/iro/test.py	Mon Feb 08 01:35:19 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest, ConfigParser
-import iro
-from job import SMSJob, FAXJob, MailJob
-from joblist import Joblist
-from providerlist import Providerlist
-import threading, xmlrpclib
-from multiprocessing import Queue
-from multiprocessing.managers import BaseManager
-
-class StoppableXMLRPCServer(iro.SecureUserDBXMLRPCServer, threading.Thread):
-    running=True
-    def __init__(self, *args, **kwargs):
-        iro.SecureUserDBXMLRPCServer.__init__(self, *args, **kwargs)
-        threading.Thread.__init__(self)
-   
-   
-    def run(self):
-        # *serve_forever* muss in einem eigenen Thread laufen, damit man es
-        # unterbrechen kann!
-        while (self.running):
-            self.handle_request()
-   
-   
-    def stop(self):
-        self.running=False
-        self.server_close()
-
-
-def init_server():
-    userlist=[{"name":"test","password":"test",  "class":iro.User},
-              {"name":"test2","password":"test2", "class": iro.User},
-              {"name":"admin","password":"admin", "class": iro.Admin}]
-
-
-    
-    class MyManager(BaseManager):
-        pass
-    
-    MyManager.register('SMSJob', SMSJob) 
-    MyManager.register('FAXob', FAXJob) 
-    MyManager.register('MailJob',MailJob) 
-    MyManager.register('Providerlist',Providerlist) 
-    manager = MyManager()
-    manager.start()
-    
-    
-    #anbieter erzeugen und konfigurieren
-    import anbieter
-    sip=anbieter.sipgate()
-    sip.read_basic_config("iro.conf")
-    
-    localhost=iro.MySMTP()
-    localhost.read_basic_config("iro.conf")
-
-    smstrade=iro.MySmstrade()
-    smstrade.read_basic_config("iro.conf")
-
-    #Benutzerdatenbank erstellen
-    queue = Queue()
-    provider=Providerlist()
-    provider.add("sipgate", sip, ["sms", "fax", ])
-    provider.add("smstrade", smstrade, ["sms", ])
-    provider.add("geonet", None, ["sms", "fax", ])
-    provider.add("fax.de", None, ["sms", "fax", ])
-    provider.add("localhost", localhost, ["mail", ])
-    provider.setDefault("sms","smstrade")
-    provider.setDefault("fax","sipgate")
-    provider.setDefault("mail","localhost")
-    jobqueue=Joblist(manager,  queue, provider)
-    userdb=iro.MyUserDB(userlist,jobqueue)
-
-
-    #Server starten
-    cp = ConfigParser.ConfigParser()
-    cp.read(["iro.conf"])
-    cert=cp.get('server', 'cert')
-    key=cp.get('server', 'key')
-    serv = StoppableXMLRPCServer(addr=("localhost", 8000), 
-                                      userdb=userdb,
-                                      certificate=cert,privatekey=key)
-    serv.relam="xmlrpc"
-    return serv
- 
-    
-class TestServer(unittest.TestCase):
-    
-    def setUp(self):
-      self.serv = init_server()
-      self.serv.start()
-
-    def tearDown(self):
-      self.serv.stop()
-      xmlrpclib.Server("https://test:test@localhost:8000").status()			#letzte nachricht abrufen, damit richt geschlossen wird
-
-    def testLogin(self):
-        self.assertEqual(xmlrpclib.Server("https://test:test@localhost:8000").status(), {})
-        self.assertEqual(xmlrpclib.Server("https://test2:test2@localhost:8000").status(), {})
-        self.assertRaises(xmlrpclib.ProtocolError, xmlrpclib.Server("https://test2:test@localhost:8000").status)
-        self.assertRaises(xmlrpclib.ProtocolError,xmlrpclib.Server ("https://test:test2@localhost:8000").status)
-    
-    def testsendSMS(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)
-        id=client.startSMS("test",["01234", ] )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name':  'test'}} )
-        
-    def testTwoUser(self):
-        u1="https://test:test@localhost:8000"
-        u2="https://test2:test2@localhost:8000"
-        admin="https://admin:admin@localhost:8000"
-        client1=xmlrpclib.Server(u1)
-        client2=xmlrpclib.Server(u2)
-        admin=xmlrpclib.Server(admin)
-        id1=client1.startSMS("test",["01234"] )
-        self.assertEqual(client2.status(),{} ) 
-        self.assertEqual(admin.status(id1),{id1: {'status': ['init',{}], 'name':  'test'}} )
-        id2=client2.startSMS("test2",["01234"] )
-        self.assertNotEqual(id1, id2)
-        self.assertEqual(client1.status(),{id1: {'status': ['init',{}], 'name':  'test'}})
-        self.assertEqual(client2.status(),{id2: {'status': ['init',{}], 'name':  'test2'}})
-        self.assertEqual(admin.status(),{id1: {'status': ['init',{}], 'name':   'test'},
-                        id2: {'status': ['init',{}], 'name':   'test2'}} )
-        
-        self.assertEqual(client2.status(id1), {})
-        self.assertEqual(client1.status(id2), {})
-        
-    def testGetProvider(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)       
-        self.assertEqual(client.getProvider("sms"), ["fax.de","geonet", "sipgate", "smstrade"])
-        self.assertEqual(client.getProvider("fax"), ["fax.de","geonet", "sipgate"])
-        self.assertEqual(client.getProvider("mail"), ["localhost"])
-        
-        self.assertRaises(xmlrpclib.Fault,client.getProvider, "temp")
-    
-    def testGetDefault(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)       
-        self.assertEqual(client.getDefaultProvider("sms"), "smstrade")
-        self.assertEqual(client.getDefaultProvider("fax"),"sipgate")
-        self.assertEqual(client.getDefaultProvider("mail"), "localhost")       
-        
-        self.assertRaises(xmlrpclib.Fault,client.getDefaultProvider, "temp")        
-
-    
-if __name__ == "__main__":
-    unittest.main()  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/testWorker.py	Wed Oct 06 04:42:23 2010 +0200
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+import logging
+from time import sleep
+
+from multiprocessing import Queue
+from multiprocessing.managers import BaseManager,ListProxy
+
+from iro.worker import Worker
+from iro.job import Job
+
+from logging.handlers import BufferingHandler
+
+class MyHandler(BufferingHandler):
+    def __init__(self,buffer=None):
+        '''BufferingHandler takes a "capacity" argument
+        so as to know when to flush. As we're overriding
+        shouldFlush anyway, we can set a capacity of zero.
+        You can call flush() manually to clear out the
+        buffer.
+            buffer: log messages to this buffer, needed f.ex for processes or threads'''
+        BufferingHandler.__init__(self, 0)
+        self.buffer=buffer
+
+    def shouldFlush(self):
+        return False
+
+    def emit(self, record):
+        if record.exc_info:                                                      #sonst geht das append schief, weil nicht picklebar
+            print record.exc_info
+            record.exc_info=record.exc_info[:-1]                                              
+        self.buffer.append(record.__dict__)
+
+
+class BadJob(Job):
+    def start(self):
+        Job.start(self)
+        raise Exception("Error")
+
+#einen Manager anlegen, der Job und eine Liste anbietet
+class MyManager(BaseManager):
+    pass
+MyManager.register('Job', Job) 
+MyManager.register('BadJob', BadJob) 
+MyManager.register('list', list, ListProxy)
+
+class TestWorker(unittest.TestCase):
+    def setUp(self):
+        #erstelle eine Queue&Manager
+        self.manager = MyManager()
+        self.queue = Queue()
+        
+        #manager starten, damit wir Logging anpassen können
+        self.manager.start()
+        self.setUpLogging()
+
+        #eigentlich Workerprocess starten
+        self.worker= Worker(self.queue)
+        self.worker.start()
+
+    def tearDown(self):
+        #Thread&Queue stoppen
+        self.stop()
+        
+        #Logging änderungen rückgängig
+        self.tearDownLogging()
+        self.manager.shutdown()
+    
+    def stop(self):
+        self.queue.close()
+        self.queue.join_thread()
+        self.worker.terminate()
+
+
+    def setUpLogging(self):
+        '''Logging so umbasteln, das wir alle logging Meldung in self.buf sind'''
+        #wir brauchen eine threadsichere liste
+        self.buffer=self.manager.list()
+        
+        #Handler erstellen, der in den Buffer schreibt
+        self.handler = h = MyHandler(self.buffer)
+        self.logger =l= logging.getLogger()
+        
+        #Level anpassen 
+        l.setLevel(logging.DEBUG)
+        h.setLevel(logging.DEBUG)
+        l.addHandler(h)
+
+    def tearDownLogging(self):
+        '''crazy logging hacks wieder entfernen'''
+        self.logger.removeHandler(self.handler)
+        self.logger.setLevel(logging.NOTSET)
+        self.handler.close()
+
+
+    def testJob(self):
+        '''einen Job verarbeiten'''
+        job=self.manager.Job(None,None,"test")
+        self.assertEqual(job.getStatus(),"init")
+        self.queue.put(job)
+        sleep(.1)
+        self.stop()
+        self.assertEqual(job.getStatus(),"started")
+        self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"],
+                [(20,'Workerprocess läuft nun...'),
+                 (20,'ein neuer Job(1)'),
+                 (20,'Job(1) fertig ;)')])
+
+    def testBadJob(self):
+        '''einen Job verarbeiten, der fehlschlägt'''
+        job=self.manager.BadJob(None,None,"test")
+        self.assertEqual(job.getStatus(),"init")
+        self.queue.put(job)
+        sleep(.1)
+        self.stop()
+        self.assertEqual(job.getStatus(),"error")
+        print self.buffer
+        self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"],
+                [(20,'Workerprocess läuft nun...'),
+                 (20,'ein neuer Job(1)'),
+                 (40,'Job(1) fehlgeschlagen :(')])
+        error=Exception('Error')
+        self.assertEqual(self.buffer[-1]['exc_info'][0],type(error))
+        self.assertEqual(str(self.buffer[-1]['exc_info'][1]),str(error))
+
+
+
+if __name__ == "__main__":
+    unittest.main()  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/testXMLRPCServer.py	Wed Oct 06 04:42:23 2010 +0200
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+
+import unittest, ConfigParser
+
+import threading, xmlrpclib
+from multiprocessing import Queue
+from multiprocessing.managers import BaseManager
+
+from iro import xmlrpc,anbieter
+from iro.iro import MySMTP,MySmstrade,MyUserDB
+from iro.job import SMSJob, FAXJob, MailJob
+from iro.joblist import Joblist
+from iro.providerlist import Providerlist
+from iro.user import User, Admin
+
+
+class StoppableXMLRPCServer(xmlrpc.SecureUserDBXMLRPCServer, threading.Thread):
+    running=False
+    def __init__(self, *args, **kwargs):
+        xmlrpc.SecureUserDBXMLRPCServer.__init__(self, *args, **kwargs)
+        threading.Thread.__init__(self)
+        self.timeout=.5
+   
+    def start(self):
+        self.running=True
+        threading.Thread.start(self)
+ 
+   
+    def run(self):
+        # *serve_forever* muss in einem eigenen Thread laufen, damit man es
+        # unterbrochen werden kann!
+        while (self.running):
+            self.handle_request()
+   
+    def stop(self):
+        if (self.running):
+            self.running=False
+            self.server_close()
+            self.join()
+
+    def __del__(self):
+        self.stop()
+ 
+    def __enter__(self):
+        self.start()
+        return self
+
+    def __exit__(self,type,value,traceback):
+        self.stop()
+
+
+
+def init_server():
+    userlist=[{"name":"test","password":"test",  "class":User},
+              {"name":"test2","password":"test2", "class": User},
+              {"name":"admin","password":"admin", "class": Admin}]
+
+
+    
+    class MyManager(BaseManager):
+        pass
+    
+    MyManager.register('SMSJob', SMSJob) 
+    MyManager.register('FAXob', FAXJob) 
+    MyManager.register('MailJob',MailJob) 
+    MyManager.register('Providerlist',Providerlist) 
+    manager = MyManager()
+    manager.start()
+    
+    
+    #anbieter erzeugen und konfigurieren
+    sip=anbieter.sipgate()
+    sip.read_basic_config("iro.conf")
+    
+    localhost=MySMTP()
+    localhost.read_basic_config("iro.conf")
+
+    smstrade=MySmstrade()
+    smstrade.read_basic_config("iro.conf")
+
+    #Benutzerdatenbank erstellen
+    queue = Queue()
+    provider=Providerlist()
+    provider.add("sipgate", sip, ["sms", "fax", ])
+    provider.add("smstrade", smstrade, ["sms", ])
+    provider.add("geonet", None, ["sms", "fax", ])
+    provider.add("fax.de", None, ["sms", "fax", ])
+    provider.add("localhost", localhost, ["mail", ])
+    provider.setDefault("sms","smstrade")
+    provider.setDefault("fax","sipgate")
+    provider.setDefault("mail","localhost")
+    jobqueue=Joblist(manager,  queue, provider)
+    userdb=MyUserDB(userlist,jobqueue)
+
+
+    #Server starten
+    cp = ConfigParser.ConfigParser()
+    cp.read(["iro.conf"])
+    cert=cp.get('server', 'cert')
+    key=cp.get('server', 'key')
+    serv = StoppableXMLRPCServer(addr=("localhost", 8000), 
+                                      userdb=userdb,
+                                      certificate=cert,privatekey=key,
+                                      logRequests=False)
+    serv.relam="xmlrpc"
+    return serv
+ 
+    
+class TestServer(unittest.TestCase):
+    
+    def setUp(self):
+        self.serv = init_server()
+        self.serv.start()
+
+    def tearDown(self):
+        self.serv.stop()
+
+    def testLogin(self):
+        self.assertEqual(xmlrpclib.Server("https://test:test@localhost:8000").status(), {})
+        self.assertEqual(xmlrpclib.Server("https://test2:test2@localhost:8000").status(), {})
+        self.assertRaises(xmlrpclib.ProtocolError, xmlrpclib.Server("https://test2:test@localhost:8000").status)
+        self.assertRaises(xmlrpclib.ProtocolError,xmlrpclib.Server ("https://test:test2@localhost:8000").status)
+    
+    def testsendSMS(self):
+        servstr="https://test:test@localhost:8000"
+        client=xmlrpclib.Server(servstr)
+        id=client.startSMS("test",["01234", ] )
+        self.assertEqual(client.status(id),{id: {'status': 'init', 'name':  'test'}} )
+        
+    def testTwoUser(self):
+        u1="https://test:test@localhost:8000"
+        u2="https://test2:test2@localhost:8000"
+        admin="https://admin:admin@localhost:8000"
+        client1=xmlrpclib.Server(u1)
+        client2=xmlrpclib.Server(u2)
+        admin=xmlrpclib.Server(admin)
+        id1=client1.startSMS("test",["01234"] )
+        self.assertEqual(client2.status(),{} ) 
+        self.assertEqual(admin.status(id1),{id1: {'status': 'init', 'name':  'test'}} )
+        id2=client2.startSMS("test2",["01234"] )
+        self.assertNotEqual(id1, id2)
+        self.assertEqual(client1.status(),{id1: {'status': 'init', 'name':  'test'}})
+        self.assertEqual(client2.status(),{id2: {'status': 'init', 'name':  'test2'}})
+        self.assertEqual(admin.status(),{id1: {'status': 'init', 'name':   'test'},
+                        id2: {'status': 'init', 'name':   'test2'}} )
+        
+        self.assertEqual(client2.status(id1), {})
+        self.assertEqual(client1.status(id2), {})
+        
+    def testGetProvider(self):
+        servstr="https://test:test@localhost:8000"
+        client=xmlrpclib.Server(servstr)       
+        self.assertEqual(client.getProvider("sms"), ["fax.de","geonet", "sipgate", "smstrade"])
+        self.assertEqual(client.getProvider("fax"), ["fax.de","geonet", "sipgate"])
+        self.assertEqual(client.getProvider("mail"), ["localhost"])
+        
+        self.assertRaises(xmlrpclib.ProtocolError,client.getProvider, "temp")
+    
+    def testGetDefault(self):
+        servstr="https://test:test@localhost:8000"
+        client=xmlrpclib.Server(servstr)       
+        self.assertEqual(client.getDefaultProvider("sms"), "smstrade")
+        self.assertEqual(client.getDefaultProvider("fax"),"sipgate")
+        self.assertEqual(client.getDefaultProvider("mail"), "localhost")       
+        
+        self.assertRaises(xmlrpclib.ProtocolError,client.getDefaultProvider, "temp")        
+
+    
+if __name__ == "__main__":
+    unittest.main()  
--- a/iro/worker.py	Mon Feb 08 01:35:19 2010 +0100
+++ b/iro/worker.py	Wed Oct 06 04:42:23 2010 +0200
@@ -4,7 +4,6 @@
 from multiprocessing import Process
 import logging
 logger = logging.getLogger("iro.worker")
-import time
 
 class Worker(Process):
     def __init__(self,queue):
@@ -12,7 +11,7 @@
         self.queue=queue
         
     def run(self):
-        logger.info('Worker thread läuft nun...')
+        logger.info('Workerprocess läuft nun...')
         id=0
         while 1:
             job=self.queue.get()
@@ -21,7 +20,9 @@
             id+=1
             logger.info('ein neuer Job(%d)' %(id))
             try:
-                job.start(id)
+                job.start()
                 logger.info('Job(%d) fertig ;)'%(id))
             except:
+                job.setStatus("error")
                 logger.exception('Job(%d) fehlgeschlagen :('%(id))
+