remove debug statements in generated code
[contrib/cloudeebus.git] / cloudeebus / cloudeebus.py
1 #!/usr/bin/env python
2
3 # Cloudeebus
4 #
5 # Copyright 2012 Intel Corporation.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19 # Luc Yriarte <luc.yriarte@intel.com>
20 # Christophe Guiraud <christophe.guiraud@intel.com>
21 #
22
23
24 import argparse, dbus, json, sys
25
26 from twisted.internet import glib2reactor
27 # Configure the twisted mainloop to be run inside the glib mainloop.
28 # This must be done before importing the other twisted modules
29 glib2reactor.install()
30 from twisted.internet import reactor, defer
31
32 from autobahn.websocket import listenWS
33 from autobahn.wamp import exportRpc, WampServerFactory, WampCraServerProtocol
34
35 from dbus.mainloop.glib import DBusGMainLoop
36
37 import gobject
38 import re
39 import dbus.service
40 gobject.threads_init()
41
42 from dbus import glib
43 glib.init_threads()
44
45 # enable debug log
46 from twisted.python import log
47
48 # XML parser module
49 from xml.etree.ElementTree import XMLParser
50
51
52 ###############################################################################
53
54 VERSION = "0.3.0"
55 OPENDOOR = False
56 CREDENTIALS = {}
57 WHITELIST = []
58
59 ###############################################################################
60 class DbusCache:
61     '''
62     Global cache of DBus connexions and signal handlers
63     '''
64     def __init__(self):
65         self.dbusConnexions = {}
66         self.signalHandlers = {}
67
68
69     def reset(self):
70         '''
71         Disconnect signal handlers before resetting cache.
72         '''
73         self.dbusConnexions = {}
74         # disconnect signal handlers
75         for key in self.signalHandlers:
76             self.signalHandlers[key].disconnect()
77         self.signalHandlers = {}
78
79
80     def dbusConnexion(self, busName):
81         if not self.dbusConnexions.has_key(busName):
82             if busName == "session":
83                 self.dbusConnexions[busName] = dbus.SessionBus()
84             elif busName == "system":
85                 self.dbusConnexions[busName] = dbus.SystemBus()
86             else:
87                 raise Exception("Error: invalid bus: %s" % busName)
88         return self.dbusConnexions[busName]
89
90
91
92 ###############################################################################
93 class DbusSignalHandler:
94     '''
95     signal hash id as busName#senderName#objectName#interfaceName#signalName
96     '''
97     def __init__(self, busName, senderName, objectName, interfaceName, signalName):
98         self.id = "#".join([busName, senderName, objectName, interfaceName, signalName])
99         # connect handler to signal
100         self.bus = cache.dbusConnexion(busName)
101         self.bus.add_signal_receiver(self.handleSignal, signalName, interfaceName, senderName, objectName)
102         
103     
104     def disconnect(self):
105         names = self.id.split("#")
106         self.bus.remove_signal_receiver(self.handleSignal, names[4], names[3], names[1], names[2])
107
108
109     def handleSignal(self, *args):
110         '''
111         publish dbus args under topic hash id
112         '''
113         factory.dispatch(self.id, json.dumps(args))
114
115
116
117 ###############################################################################
118 class DbusCallHandler:
119     '''
120     deferred reply to return dbus results
121     '''
122     def __init__(self, method, args):
123         self.pending = False
124         self.request = defer.Deferred()
125         self.method = method
126         self.args = args
127
128
129     def callMethod(self):
130         '''
131         dbus method async call
132         '''
133         self.pending = True
134         self.method(*self.args, reply_handler=self.dbusSuccess, error_handler=self.dbusError)
135         return self.request
136
137
138     def dbusSuccess(self, *result):
139         '''
140         return JSON string result array
141         '''
142         self.request.callback(json.dumps(result))
143         self.pending = False
144
145
146     def dbusError(self, error):
147         '''
148         return dbus error message
149         '''
150         self.request.errback(Exception(error.get_dbus_message()))
151         self.pending = False
152
153
154
155 ################################################################################       
156 class exec_code:
157     def __init__(self, globalCtx, localCtx) :
158         self.exec_string = ""
159         self.exec_code = None
160         self.exec_code_valid = 1
161         self.indent_level = 0
162         self.indent_increment = 1
163         self.line = 0
164         self.localCtx = localCtx
165         self.globalCtx = globalCtx
166         
167
168     def append_stmt(self, stmt) :
169         self.exec_code_valid = 0
170         self.line += 1
171         for x in range(0,self.indent_level):
172             self.exec_string = self.exec_string + ' '            
173         self.exec_string = self.exec_string + stmt + '\n'
174
175     def indent(self) :
176         self.indent_level = self.indent_level + self.indent_increment
177
178     def dedent(self) :
179         self.indent_level = self.indent_level - self.indent_increment
180     
181     # compile : Compile exec_string into exec_code using the builtin
182     # compile function. Skip if already in sync.
183     def compile(self) :
184         if not self.exec_code_valid :
185             self.exec_code = compile(self.exec_string, "<string>", "exec")
186         self.exec_code_valid = True
187
188     def execute(self) :
189         if not self.exec_code_valid :
190             self.compile()
191         exec(self.exec_code, self.globalCtx, self.localCtx)
192
193
194
195 ################################################################################       
196 class XmlCb_Parser: # The target object of the parser
197     maxDepth = 0
198     depth = 0
199     def __init__(self, dynDBusClass):
200         self.dynDBusClass = dynDBusClass
201         
202     def start(self, tag, attrib):   # Called for each opening tag.
203         if (tag == 'node'):
204             return
205         # Set interface name
206         if (tag == 'interface'):
207             self.dynDBusClass.set_interface(attrib['name'])
208             return
209         # Set method name
210         if (tag == 'method'):
211             self.current = tag
212             self.dynDBusClass.def_method(attrib['name'])
213             return
214         if (tag == 'signal'):
215             self.current = tag
216             self.dynDBusClass.def_signal(attrib['name'])
217             return
218
219         # Set signature (in/out & name) for method
220         if (tag == 'arg'):
221             if (self.current == 'method'):
222                 if (attrib.has_key('direction') == False):
223                     attrib['direction'] = "in"
224                 self.dynDBusClass.add_signature(attrib['name'],
225                                                 attrib['direction'],
226                                                 attrib['type'])
227                 return
228             if (self.current == 'signal'):
229                 self.dynDBusClass.add_signature(attrib['name'], 'in',
230                                                 attrib['type'])
231                 return
232     def end(self, tag):             # Called for each closing tag.
233         if (tag == 'method'):
234             self.dynDBusClass.add_dbus_method()
235             self.dynDBusClass.add_body_method()
236             self.dynDBusClass.end_method()
237         if (tag == 'signal'):
238             self.dynDBusClass.add_dbus_signal()
239             self.dynDBusClass.add_body_signal()
240             self.dynDBusClass.end_method()
241            
242     def data(self, data):
243         pass            # We do not need to do anything with data.
244     def close(self):    # Called when all data has been parsed.
245         return self.maxDepth
246
247
248        
249 ################################################################################       
250 class dynDBusClass():
251     def __init__(self, className, globalCtx, localCtx):
252         self.className = className
253         self.xmlCB = XmlCb_Parser(self)
254         self.signature = {}
255         self.class_code = exec_code(globalCtx, localCtx)  
256         self.class_code.indent_increment = 4
257         self.class_code.append_stmt("import dbus")
258         self.class_code.append_stmt("\n")
259         self.class_code.append_stmt("class " + self.className + "(dbus.service.Object):")
260         self.class_code.indent()
261         
262         ## Overload of __init__ method 
263         self.def_method("__init__")
264         self.add_method("bus, callback=None, objName='/sample', busName='org.cloudeebus'")
265         self.add_stmt("self.bus = bus")
266         self.add_stmt("self.objName = objName")
267         self.add_stmt("self.callback = callback")        
268         self.add_stmt("dbus.service.Object.__init__(self, conn=bus, bus_name=busName)")
269         self.end_method()
270                
271         ## Create 'add_to_connection' method 
272         self.def_method("add_to_connection")
273         self.add_method("connection=None, path=None")
274         self.add_stmt("dbus.service.Object.add_to_connection(self, connection=self.bus, path=self.objName)")
275         self.end_method()
276                
277         ## Create 'remove_from_connection' method 
278         self.def_method("remove_from_connection")
279         self.add_method("connection=None, path=None")
280         self.add_stmt("dbus.service.Object.remove_from_connection(self, connection=None, path=self.objName)")
281         self.end_method()
282                
283     def createDBusServiceFromXML(self, xml):
284         self.parser = XMLParser(target=self.xmlCB)
285         self.parser.feed(xml)
286         self.parser.close()
287     
288     def set_interface(self, ifName):
289         self.ifName = ifName
290         
291     def def_method(self, methodName):
292         self.methodToAdd = methodName
293         self.signalToAdd = None
294         self.args_str = str()
295         self.signature = {}
296         self.signature['name'] = str()
297         self.signature['in'] = str()                
298         self.signature['out'] = str()                        
299
300     def def_signal(self, signalName):
301         self.methodToAdd = None
302         self.signalToAdd = signalName
303         self.args_str = str()
304         self.signature = {}
305         self.signature['name'] = str()
306         self.signature['in'] = str()                
307         self.signature['out'] = str()                        
308
309     def add_signature(self, name, direction, signature):
310         if (direction == 'in'):
311             self.signature['in'] += signature
312             if (self.signature['name'] != str()):
313                 self.signature['name'] += ", "
314             self.signature['name'] += name
315         if (direction == 'out'):
316             self.signature['out'] = signature                        
317         
318     def add_method(self, args = None, async_success_cb = None, async_err_cb = None):
319         async_cb_str = str()
320         if (self.methodToAdd != None):
321             name = self.methodToAdd
322         else:
323             name = self.signalToAdd
324         if (args != None):
325             self.args_str = args
326         if (async_success_cb != None):
327             async_cb_str = async_success_cb
328         if (async_err_cb != None):
329             if (async_cb_str != str()):
330                 async_cb_str += ", "
331             async_cb_str += async_err_cb
332                         
333         parameters = self.args_str
334         if (async_cb_str != str()):
335             if (parameters != str()):
336                 parameters += ", "
337             parameters +=async_cb_str       
338         
339         if (parameters != str()):
340             self.class_code.append_stmt("def " + name + "(self, %s):" % parameters)               
341         else:
342             self.class_code.append_stmt("def " + name + "(self):")
343         self.class_code.indent()
344         
345     def end_method(self):
346         self.class_code.append_stmt("\n")
347         self.class_code.dedent()
348         
349     def add_dbus_method(self):
350         decorator = '@dbus.service.method("' + self.ifName + '"'
351         if (self.signature.has_key('in') and self.signature['in'] != str()):
352                 decorator += ", in_signature='" + self.signature['in'] + "'"
353         if (self.signature.has_key('out') and self.signature['out'] != str()):
354                 decorator += ", out_signature='" + self.signature['out'] + "'"
355         decorator += ", async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')"            
356         decorator += ")"
357         self.class_code.append_stmt(decorator)
358         if (self.signature.has_key('name') and self.signature['name'] != str()):
359             self.add_method(self.signature['name'], async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
360         else:
361             self.add_method(async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
362
363     def add_dbus_signal(self):
364         decorator = '@dbus.service.signal("' + self.ifName + '"'
365         if (self.signature.has_key('in') and self.signature['in'] != str()):
366                 decorator += ", signature='" + self.signature['in'] + "'"
367         decorator += ")"            
368         self.class_code.append_stmt(decorator)
369         if (self.signature.has_key('name') and self.signature['name'] != str()):
370             self.add_method(self.signature['name'])
371         else:
372             self.add_method()
373
374     def add_body_method(self):
375         if (self.methodToAdd != None):
376             self.class_code.append_stmt("print 'In " + self.methodToAdd + "()'")
377             if (self.args_str != str()):
378                 self.class_code.append_stmt("self.callback('" + self.methodToAdd + "', dbus_async_cb, dbus_async_err_cb, %s)" % self.args_str)
379             else:        
380                 self.class_code.append_stmt("self.callback('" + self.methodToAdd + "', dbus_async_cb, dbus_async_err_cb)")
381
382     def add_body_signal(self):
383         self.class_code.append_stmt("return") ## TODO: Remove and fix with code ad hoc
384         self.class_code.append_stmt("\n")
385
386     def add_stmt(self, stmt) :
387         self.class_code.append_stmt(stmt)
388         
389     def declare(self) :
390         self.class_code.execute()
391
392
393
394 ###############################################################################
395 class CloudeebusService:
396     '''
397     support for sending DBus messages and registering for DBus signals
398     '''
399     def __init__(self, permissions):
400         self.permissions = permissions;
401         self.proxyObjects = {}
402         self.proxyMethods = {}
403         self.pendingCalls = []
404         self.dynDBusClasses = {} # DBus class source code generated dynamically (a list because one by classname)
405         self.services = {}  # DBus service created
406         self.serviceAgents = {} # Instantiated DBus class previously generated dynamically, for now, one by classname
407         self.servicePendingCalls = {} # JS methods called (and waiting for a Success/error response), containing 'methodId', (successCB, errorCB)
408         self.localCtx = locals()
409         self.globalCtx = globals()
410
411
412     def proxyObject(self, busName, serviceName, objectName):
413         '''
414         object hash id as busName#serviceName#objectName
415         '''
416         id = "#".join([busName, serviceName, objectName])
417         if not self.proxyObjects.has_key(id):
418             if not OPENDOOR:
419                 # check permissions, array.index throws exception
420                 self.permissions.index(serviceName)
421             bus = cache.dbusConnexion(busName)
422             self.proxyObjects[id] = bus.get_object(serviceName, objectName)
423         return self.proxyObjects[id]
424
425
426     def proxyMethod(self, busName, serviceName, objectName, interfaceName, methodName):
427         '''
428         method hash id as busName#serviceName#objectName#interfaceName#methodName
429         '''
430         id = "#".join([busName, serviceName, objectName, interfaceName, methodName])
431         if not self.proxyMethods.has_key(id):
432             obj = self.proxyObject(busName, serviceName, objectName)
433             self.proxyMethods[id] = obj.get_dbus_method(methodName, interfaceName)
434         return self.proxyMethods[id]
435
436
437     @exportRpc
438     def dbusRegister(self, list):
439         '''
440         arguments: bus, sender, object, interface, signal
441         '''
442         if len(list) < 5:
443             raise Exception("Error: expected arguments: bus, sender, object, interface, signal)")
444         
445         if not OPENDOOR:
446             # check permissions, array.index throws exception
447             self.permissions.index(list[1])
448         
449         # check if a handler exists
450         sigId = "#".join(list)
451         if cache.signalHandlers.has_key(sigId):
452             return sigId
453         
454         # create a handler that will publish the signal
455         dbusSignalHandler = DbusSignalHandler(*list)
456         cache.signalHandlers[sigId] = dbusSignalHandler
457         
458         return dbusSignalHandler.id
459
460
461     @exportRpc
462     def dbusSend(self, list):
463         '''
464         arguments: bus, destination, object, interface, message, [args]
465         '''
466         # clear pending calls
467         for call in self.pendingCalls:
468             if not call.pending:
469                 self.pendingCalls.remove(call)
470         
471         if len(list) < 5:
472             raise Exception("Error: expected arguments: bus, destination, object, interface, message, [args])")
473         
474         # parse JSON arg list
475         args = []
476         if len(list) == 6:
477             args = json.loads(list[5])
478         
479         # get dbus proxy method
480         method = self.proxyMethod(*list[0:5])
481         
482         # use a deferred call handler to manage dbus results
483         dbusCallHandler = DbusCallHandler(method, args)
484         self.pendingCalls.append(dbusCallHandler)
485         return dbusCallHandler.callMethod()
486
487
488     @exportRpc
489     def returnMethod(self, list):
490         '''
491         arguments: methodId, success (=true, error otherwise), result (to return)
492         '''
493         methodId = list[0]
494         success = list[1]
495         result = list[2]
496         if (self.servicePendingCalls.has_key(methodId)):
497             cb = self.servicePendingCalls[methodId]
498             if (success):                
499                 successCB = cb["successCB"]
500                 if (result != None):
501                     successCB(result)
502                 else:
503                     successCB()                    
504             else:     
505                 errorCB = cb["errorCB"]        
506                 if (result != None):
507                     errorCB(result)
508                 else:
509                     errorCB()
510             self.servicePendingCalls[methodId] = None
511         else:
512             print "No methodID %s  !!" % (methodId)  
513
514     def jsonEncodeTupleKeyDict(self, data):
515         ndict = dict()
516         # creates new dictionary with the original tuple converted to json string
517         dataLen = len(data)
518         string = ""
519         for index in range(dataLen):
520             for key in data[index]:
521                 value = data[index][key]
522                 print "key=" + key
523                 print "value=" + str(value)
524                 nkey = str(key)
525                 nvalue = ""
526                 print "JSON key=" + nkey
527                 if (isinstance(value, dbus.Array)):
528                     # Searching dbus byte in array...
529                     ValueLen = len(value)
530                     nvalue = []
531                     for indexValue in range(ValueLen):
532                         a = value[indexValue]
533                         if (isinstance(a, dbus.Byte)):
534                             a = int(value[indexValue])
535                             nvalue.append(a)
536                         else:
537                             nvalue = str(value[indexValue])
538                             
539                 print "JSON value=" + str(nvalue)                
540                 ndict[nkey] =  nvalue
541
542         return ndict
543
544     def srvCB(self, name, async_succes_cb, async_error_cb, *args):
545         methodId = self.srvName + "#" + self.agentObjectPath + "#" + name
546         cb = { 'successCB': async_succes_cb, 
547                'errorCB': async_error_cb}
548         self.servicePendingCalls[methodId] = cb
549
550         if (len(args) > 0):
551             print "Received args=%s" % (str(args))
552         else:                     
553             print "No args received"
554             
555         try:               
556             print "factory.dispatch(methodId=%s, args=%s)" % (methodId, json.dumps(args))                     
557             factory.dispatch(methodId, json.dumps(args))
558             return
559         except Exception, e :
560             print "Error=%s" % (str(e))
561             
562         print "Trying to decode dbus.Dictionnary..."
563         try:
564             params = self.jsonEncodeTupleKeyDict(args)                
565             print "factory.dispatch(methodId=%s, args=%s)" % (methodId, params)                     
566             factory.dispatch(methodId, params)
567             return
568         except Exception, e :
569             print "Error=%s" % (str(e))
570                     
571         print "Trying to pass args as string..."
572         try:               
573             print "factory.dispatch(methodId=%s, args=%s)" % (methodId, str(args))                     
574             factory.dispatch(methodId, str(args))
575             return
576         except Exception, e :
577             print "Error=%s" % (str(e))
578                     
579     @exportRpc
580     def serviceAdd(self, list):
581         '''
582         arguments: busName, srvName
583         '''
584         busName = list[0]
585         self.bus =  cache.dbusConnexion( busName['name'] )
586         self.srvName = list[1]
587         if (self.services.has_key(self.srvName) == False):            
588             self.services[self.srvName] = dbus.service.BusName(name = self.srvName, bus = self.bus)
589         return self.srvName
590
591     @exportRpc
592     def serviceRelease(self, list):
593         '''
594         arguments: busName, srvName
595         '''
596         self.srvName = list[0]
597         if (self.services.has_key(self.srvName) == True):
598             self.services.pop(self.srvName)
599             return self.srvName
600         else:
601             raise Exception(self.srvName + " do not exist")
602                    
603     @exportRpc
604     def serviceAddAgent(self, list):
605         '''
606         arguments: objectPath, xmlTemplate
607         '''
608         self.agentObjectPath = list[0]
609         xmlTemplate = list[1]
610         self.className = re.sub('/', '_', self.agentObjectPath[1:])
611         if (self.dynDBusClasses.has_key(self.className) == False):
612             self.dynDBusClasses[self.className] = dynDBusClass(self.className, self.globalCtx, self.localCtx)
613             self.dynDBusClasses[self.className].createDBusServiceFromXML(xmlTemplate)
614             self.dynDBusClasses[self.className].declare()
615
616         ## Class already exist, instanciate it if not already instanciated
617         if (self.serviceAgents.has_key(self.className) == False):
618             self.serviceAgents[self.className] = eval(self.className + "(self.bus, callback=self.srvCB, objName=self.agentObjectPath, busName=self.srvName)", self.globalCtx, self.localCtx)
619             
620         self.serviceAgents[self.className].add_to_connection()
621         return (self.agentObjectPath)
622                     
623     @exportRpc
624     def serviceDelAgent(self, list):
625         '''
626         arguments: objectPath, xmlTemplate
627         '''
628         agentObjectPath = list[0]
629         className = re.sub('/', '_', agentObjectPath[1:])
630
631         if (self.serviceAgents.has_key(className)):
632             self.serviceAgents[self.className].remove_from_connection()
633             self.serviceAgents.pop(self.className)
634         else:
635             raise Exception(agentObjectPath + " doesn't exist!")
636         
637         return (agentObjectPath)
638                     
639     @exportRpc
640     def getVersion(self):
641         '''
642         return current version string
643         '''
644         return VERSION
645
646
647
648 ###############################################################################
649 class CloudeebusServerProtocol(WampCraServerProtocol):
650     '''
651     connexion and session authentication management
652     '''
653     
654     def onSessionOpen(self):
655         # CRA authentication options
656         self.clientAuthTimeout = 0
657         self.clientAuthAllowAnonymous = OPENDOOR
658         # CRA authentication init
659         WampCraServerProtocol.onSessionOpen(self)
660     
661     
662     def getAuthPermissions(self, key, extra):
663         return json.loads(extra.get("permissions", "[]"))
664     
665     
666     def getAuthSecret(self, key):
667         secret = CREDENTIALS.get(key, None)
668         if secret is None:
669             return None
670         # secret must be of str type to be hashed
671         return secret.encode('utf-8')
672     
673
674     def onAuthenticated(self, key, permissions):
675         if not OPENDOOR:
676             # check authentication key
677             if key is None:
678                 raise Exception("Authentication failed")
679             # check permissions, array.index throws exception
680             for req in permissions:
681                 WHITELIST.index(req)
682         # create cloudeebus service instance
683         self.cloudeebusService = CloudeebusService(permissions)
684         # register it for RPC
685         self.registerForRpc(self.cloudeebusService)
686         # register for Publish / Subscribe
687         self.registerForPubSub("", True)
688     
689     
690     def connectionLost(self, reason):
691         WampCraServerProtocol.connectionLost(self, reason)
692         if factory.getConnectionCount() == 0:
693             cache.reset()
694
695
696
697 ###############################################################################
698
699 if __name__ == '__main__':
700     
701     cache = DbusCache()
702
703     parser = argparse.ArgumentParser(description='Javascript DBus bridge.')
704     parser.add_argument('-v', '--version', action='store_true', 
705         help='print version and exit')
706     parser.add_argument('-d', '--debug', action='store_true', 
707         help='log debug info on standard output')
708     parser.add_argument('-o', '--opendoor', action='store_true',
709         help='allow anonymous access to all services')
710     parser.add_argument('-p', '--port', default='9000',
711         help='port number')
712     parser.add_argument('-c', '--credentials',
713         help='path to credentials file')
714     parser.add_argument('-w', '--whitelist',
715         help='path to whitelist file')
716     
717     args = parser.parse_args(sys.argv[1:])
718
719     if args.version:
720         print("Cloudeebus version " + VERSION)
721         exit(0)
722     
723     if args.debug:
724         log.startLogging(sys.stdout)
725     
726     OPENDOOR = args.opendoor
727     
728     if args.credentials:
729         jfile = open(args.credentials)
730         CREDENTIALS = json.load(jfile)
731         jfile.close()
732     
733     if args.whitelist:
734         jfile = open(args.whitelist)
735         WHITELIST = json.load(jfile)
736         jfile.close()
737     
738     uri = "ws://localhost:" + args.port
739     
740     factory = WampServerFactory(uri, debugWamp = args.debug)
741     factory.protocol = CloudeebusServerProtocol
742     factory.setProtocolOptions(allowHixie76 = True)
743     
744     listenWS(factory)
745     
746     DBusGMainLoop(set_as_default=True)
747     
748     reactor.run()