rpc.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814
  1. import xmlrpclib
  2. import os
  3. from supervisord import ProcessStates
  4. from supervisord import SupervisorStates
  5. from supervisord import getSupervisorStateDescription
  6. import doctags
  7. import signal
  8. import time
  9. from medusa.xmlrpc_handler import xmlrpc_handler
  10. from medusa.http_server import get_header
  11. from medusa import producers
  12. import sys
  13. import types
  14. import re
  15. import traceback
  16. import StringIO
  17. import tempfile
  18. import errno
  19. from http import NOT_DONE_YET
  20. RPC_VERSION = 1.0
  21. FAULTS = {
  22. 'SUPER_READ_NO_FILE':1000,
  23. 'SUPER_READ_BAD_ARGUMENTS':1001,
  24. 'SUPER_CLEAR_FAILED':1010,
  25. 'SUPER_CLEAR_NO_FILE':1011,
  26. 'START_BAD_NAME':2002,
  27. 'START_ABNORMAL_TERMINATION':2003,
  28. 'START_SPAWN_ERROR':2004,
  29. 'START_ALREADY_STARTED':2006,
  30. 'STOP_BAD_NAME':2010,
  31. 'STOP_UNSUCCESSFUL':2011,
  32. 'STOP_NOT_RUNNING':2012,
  33. 'INFO_BAD_NAME':2020,
  34. 'READ_BAD_NAME':2040,
  35. 'READ_BAD_ARGUMENTS':2041,
  36. 'READ_NO_FILE':2042,
  37. 'CLEAR_BAD_NAME':2050,
  38. 'CLEAR_FAILED':2051,
  39. 'UNKNOWN_METHOD':1,
  40. 'INCORRECT_PARAMETERS':2,
  41. 'SIGNATURE_UNSUPPORTED':4,
  42. 'SHUTDOWN_STATE':6,
  43. }
  44. RFAULTS = dict([(v, k) for k, v in FAULTS.items()])
  45. class RPCError(Exception):
  46. def __init__(self, text, code=None):
  47. self.text = text
  48. if code is None:
  49. self.code = FAULTS[text]
  50. else:
  51. self.code = code
  52. class DeferredXMLRPCResponse:
  53. """ A medusa producer that implements a deferred callback; requires
  54. a subclass of asynchat.async_chat that handles NOT_DONE_YET sentinel """
  55. CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
  56. def __init__(self, request, callback):
  57. self.callback = callback
  58. self.request = request
  59. self.finished = False
  60. self.delay = float(callback.delay)
  61. def more(self):
  62. if self.finished:
  63. return ''
  64. try:
  65. try:
  66. value = self.callback()
  67. if value is NOT_DONE_YET:
  68. return NOT_DONE_YET
  69. except RPCError, err:
  70. value = xmlrpclib.Fault(err.code, err.text)
  71. body = xmlrpc_marshal(value)
  72. self.finished = True
  73. return self.getresponse(body)
  74. except:
  75. # report unexpected exception back to server
  76. traceback.print_exc()
  77. self.finished = True
  78. self.request.error(500)
  79. def getresponse(self, body):
  80. self.request['Content-Type'] = 'text/xml'
  81. self.request['Content-Length'] = len(body)
  82. self.request.push(body)
  83. connection = get_header(self.CONNECTION, self.request.header)
  84. close_it = 0
  85. wrap_in_chunking = 0
  86. if self.request.version == '1.0':
  87. if connection == 'keep-alive':
  88. if not self.request.has_key ('Content-Length'):
  89. close_it = 1
  90. else:
  91. self.request['Connection'] = 'Keep-Alive'
  92. else:
  93. close_it = 1
  94. elif self.request.version == '1.1':
  95. if connection == 'close':
  96. close_it = 1
  97. elif not self.request.has_key ('Content-Length'):
  98. if self.request.has_key ('Transfer-Encoding'):
  99. if not self.request['Transfer-Encoding'] == 'chunked':
  100. close_it = 1
  101. elif self.request.use_chunked:
  102. self.request['Transfer-Encoding'] = 'chunked'
  103. wrap_in_chunking = 1
  104. else:
  105. close_it = 1
  106. elif self.request.version is None:
  107. close_it = 1
  108. outgoing_header = producers.simple_producer (
  109. self.request.build_reply_header())
  110. if close_it:
  111. self.request['Connection'] = 'close'
  112. if wrap_in_chunking:
  113. outgoing_producer = producers.chunked_producer (
  114. producers.composite_producer (self.request.outgoing)
  115. )
  116. # prepend the header
  117. outgoing_producer = producers.composite_producer(
  118. [outgoing_header, outgoing_producer]
  119. )
  120. else:
  121. # prepend the header
  122. self.request.outgoing.insert(0, outgoing_header)
  123. outgoing_producer = producers.composite_producer (
  124. self.request.outgoing)
  125. # apply a few final transformations to the output
  126. self.request.channel.push_with_producer (
  127. # globbing gives us large packets
  128. producers.globbing_producer (
  129. # hooking lets us log the number of bytes sent
  130. producers.hooked_producer (
  131. outgoing_producer,
  132. self.request.log
  133. )
  134. )
  135. )
  136. self.request.channel.current_request = None
  137. if close_it:
  138. self.request.channel.close_when_done()
  139. def xmlrpc_marshal(value):
  140. ismethodresponse = not isinstance(value, xmlrpclib.Fault)
  141. if ismethodresponse:
  142. if not isinstance(value, tuple):
  143. value = (value,)
  144. body = xmlrpclib.dumps(value, methodresponse=ismethodresponse)
  145. else:
  146. body = xmlrpclib.dumps(value)
  147. return body
  148. class SupervisorNamespaceRPCInterface:
  149. def __init__(self, supervisord):
  150. self.supervisord = supervisord
  151. def _update(self, text):
  152. self.update_text = text # for unit tests, mainly
  153. state = self.supervisord.get_state()
  154. if state == SupervisorStates.SHUTDOWN:
  155. raise RPCError('SHUTDOWN_STATE')
  156. # RPC API methods
  157. def getVersion(self):
  158. """ Return the version of the RPC API used by supervisord
  159. @return int version version id
  160. """
  161. self._update('getVersion')
  162. return RPC_VERSION
  163. def getIdentification(self):
  164. """ Return identifiying string of supervisord
  165. @return string identifier identifying string
  166. """
  167. self._update('getIdentification')
  168. return self.supervisord.options.identifier
  169. def getState(self):
  170. """ Return current state of supervisord as a struct
  171. @return struct A struct with keys string statecode, int statename
  172. """
  173. self._update('getState')
  174. state = self.supervisord.get_state()
  175. statename = getSupervisorStateDescription(state)
  176. data = {
  177. 'statecode':state,
  178. 'statename':statename,
  179. }
  180. return data
  181. def readLog(self, offset, length):
  182. """ Read length bytes from the main log starting at offset.
  183. @param int offset offset to start reading from.
  184. @param int length number of bytes to read from the log.
  185. @return struct data a struct with keys 'log' (value of 'log' is string).
  186. """
  187. self._update('readLog')
  188. logfile = self.supervisord.options.logfile
  189. if logfile is None or not os.path.exists(logfile):
  190. raise RPCError('SUPER_READ_NO_FILE')
  191. try:
  192. return _readFile(logfile, offset, length)
  193. except ValueError, inst:
  194. why = inst.args[0]
  195. raise RPCError('SUPER_READ_' + why)
  196. def clearLog(self):
  197. """ Clear the main log.
  198. @return boolean result always returns True unless error
  199. """
  200. self._update('clearLog')
  201. logfile = self.supervisord.options.logfile
  202. if logfile is None or not os.path.exists(logfile):
  203. raise RPCError('SUPER_CLEAR_NO_FILE')
  204. try:
  205. os.remove(logfile) # there is a race condition here, but ignore it.
  206. except (os.error, IOError):
  207. raise RPCError('SUPER_CLEAR_FAILED')
  208. for handler in self.supervisord.options.logger.handlers:
  209. if hasattr(handler, 'reopen'):
  210. self.supervisord.options.logger.info('reopening log file')
  211. handler.reopen()
  212. return True
  213. def shutdown(self):
  214. """ Shut down the supervisor process
  215. @return boolean result always returns True unless error
  216. """
  217. self._update('shutdown')
  218. self.supervisord.mood = -1
  219. return True
  220. def restart(self):
  221. """ Restart the supervisor process
  222. @return boolean result always return True unless error
  223. """
  224. self._update('restart')
  225. self.supervisord.mood = 0
  226. return True
  227. def startProcess(self, name, timeout=500):
  228. """ Start a process
  229. @param string name Process name
  230. @param int timeout Number of milliseconds to wait for process start
  231. @return boolean result Always true unless error
  232. """
  233. self._update('startProcess')
  234. processes = self.supervisord.processes
  235. process = processes.get(name)
  236. if process is None:
  237. raise RPCError('START_BAD_NAME')
  238. if process.pid:
  239. raise RPCError('START_ALREADY_STARTED')
  240. process.spawn()
  241. if process.spawnerr:
  242. raise RPCError('START_SPAWN_ERROR')
  243. if not timeout:
  244. return True
  245. milliseconds = timeout / 1000.0
  246. start = time.time()
  247. def check_still_running(done=False): # done arg is only for unit testing
  248. t = time.time()
  249. runtime = (t - start)
  250. if not done and runtime < milliseconds:
  251. return NOT_DONE_YET
  252. pid = processes[name].pid
  253. if pid:
  254. return True
  255. raise RPCError('START_ABNORMAL_TERMINATION')
  256. check_still_running.delay = milliseconds
  257. check_still_running.rpcinterface = self
  258. return check_still_running # deferred
  259. def startAllProcesses(self, timeout=500):
  260. """ Start all processes listed in the configuration file
  261. @param int timeout Number of milliseconds to wait for each process start
  262. @return boolean result Always true unless error
  263. """
  264. self._update('startAllProcesses')
  265. processes = self.supervisord.processes
  266. callbacks = []
  267. processnames = processes.keys()
  268. processnames.sort()
  269. for processname in processnames:
  270. process = processes[processname]
  271. if process.get_state() != ProcessStates.RUNNING:
  272. # only start nonrunning processes
  273. callbacks.append(self.startProcess(processname, timeout))
  274. def startall(done=False): # done arg is for unit testing
  275. if not callbacks:
  276. return True
  277. callback = callbacks.pop(0)
  278. value = callback(done)
  279. if value is NOT_DONE_YET:
  280. # push it back into the queue; it will finish eventually
  281. callbacks.append(callback)
  282. if callbacks:
  283. return NOT_DONE_YET
  284. return True
  285. # XXX the above implementation has a weakness inasmuch as the
  286. # first call into each individual process callback will always
  287. # return NOT_DONE_YET, so they need to be called twice. The
  288. # symptom of this is that calling this method causes the
  289. # client to block for much longer than it actually requires to
  290. # start all of the nonrunning processes. See stopAllProcesses
  291. startall.delay = 0.05
  292. startall.rpcinterface = self
  293. return startall # deferred
  294. def stopProcess(self, name):
  295. """ Stop a process named by name
  296. @param string name The name of the process to stop
  297. @return boolean result Always return True unless error
  298. """
  299. self._update('stopProcess')
  300. process = self.supervisord.processes.get(name)
  301. if process is None:
  302. raise RPCError('STOP_BAD_NAME')
  303. if process.get_state() != ProcessStates.RUNNING:
  304. raise RPCEror('STOP_NOT_RUNNING')
  305. def killit():
  306. if process.killing:
  307. return NOT_DONE_YET
  308. elif process.pid:
  309. msg = process.stop()
  310. if msg is not None:
  311. raise RPCError('STOP_UNSUCCESSFUL')
  312. return NOT_DONE_YET
  313. else:
  314. return True
  315. killit.delay = 0.2
  316. killit.rpcinterface = self
  317. return killit # deferred
  318. def stopAllProcesses(self):
  319. """ Stop all processes in the process list
  320. @return boolean result Always return true unless error.
  321. """
  322. self._update('stopAllProcesses')
  323. processes = self.supervisord.processes
  324. callbacks = []
  325. processnames = processes.keys()
  326. processnames.sort()
  327. for processname in processnames:
  328. process = processes[processname]
  329. if process.pid:
  330. # only stop running processes
  331. callbacks.append(self.stopProcess(processname))
  332. def killall():
  333. if not callbacks:
  334. return True
  335. callback = callbacks.pop(0)
  336. try:
  337. value = callback()
  338. except RPCError, inst:
  339. if inst.text == 'STOP_UNSUCCESSFUL':
  340. raise RPCError('STOP_ALL_UNSUCCESSFUL')
  341. if value is NOT_DONE_YET:
  342. # push it back into the queue; it will finish eventually
  343. callbacks.append(callback)
  344. if callbacks:
  345. return NOT_DONE_YET
  346. return True
  347. # XXX the above implementation has a weakness inasmuch as the
  348. # first call into each individual process callback will always
  349. # return NOT_DONE_YET, so they need to be called twice. The
  350. # symptom of this is that calling this method causes the
  351. # client to block for much longer than it actually requires to
  352. # kill all of the running processes. After the first call to
  353. # the killit callback, the process is actually dead, but the
  354. # above killall method processes the callbacks one at a time
  355. # during the select loop, which, because there is no output
  356. # from child processes after stopAllProcesses is called, is
  357. # not busy, so hits the timeout for each callback. I
  358. # attempted to make this better, but the only way to make it
  359. # better assumes totally synchronous reaping of child
  360. # processes, which requires infrastructure changes to
  361. # supervisord that are scary at the moment as it could take a
  362. # while to pin down all of the platform differences and might
  363. # require a C extension to the Python signal module to allow
  364. # the setting of ignore flags to signals.
  365. killall.delay = 0.05
  366. killall.rpcinterface = self
  367. return killall # deferred
  368. def getProcessInfo(self, name):
  369. """ Get info about a process named name
  370. @param string name The name of the process
  371. @return struct result A structure containing data about the process
  372. """
  373. self._update('getProcessInfo')
  374. process = self.supervisord.processes.get(name)
  375. if process is None:
  376. raise RPCError('INFO_BAD_NAME')
  377. start = int(process.laststart)
  378. stop = int(process.laststop)
  379. now = int(time.time())
  380. state = process.get_state()
  381. spawnerr = process.spawnerr or ''
  382. exitstatus = process.exitstatus or 0
  383. reportstatusmsg = process.reportstatusmsg or ''
  384. return {
  385. 'name':name,
  386. 'start':start,
  387. 'stop':stop,
  388. 'now':now,
  389. 'state':state,
  390. 'spawnerr':spawnerr,
  391. 'exitstatus':exitstatus,
  392. 'reportstatusmsg':reportstatusmsg,
  393. 'logfile':process.config.logfile,
  394. 'pid':process.pid
  395. }
  396. def getAllProcessInfo(self):
  397. """ Get info about all processes
  398. @return array result An array of process status results
  399. """
  400. self._update('getAllProcessInfo')
  401. processnames = self.supervisord.processes.keys()
  402. processnames.sort()
  403. output = []
  404. for processname in processnames:
  405. output.append(self.getProcessInfo(processname))
  406. return output
  407. def readProcessLog(self, processName, offset, length):
  408. """ Read length bytes from processName's log starting at offset
  409. @param string processName The name of the process
  410. @param int offset offset to start reading from.
  411. @param int length number of bytes to read from the log.
  412. @return string result Bytes of log
  413. """
  414. self._update('readProcessLog')
  415. process = self.supervisord.processes.get(processName)
  416. if process is None:
  417. raise RPCError('READ_BAD_NAME')
  418. logfile = process.config.logfile
  419. if logfile is None or not os.path.exists(logfile):
  420. # XXX problematic: processes that don't start won't have a log
  421. # file and we probably don't want to go into fatal state if we try
  422. # to read the log of a process that did not start.
  423. raise RPCError('READ_NO_FILE')
  424. try:
  425. return _readFile(logfile, offset, length)
  426. except ValueError, inst:
  427. why = inst.args[0]
  428. raise RPCError('READ_' + why)
  429. def clearProcessLog(self, processName):
  430. """ Clear the log for processName and reopen it
  431. @param string processName The name of the process
  432. @return boolean result Always True unless error
  433. """
  434. self._update('clearProcessLog')
  435. process = self.supervisord.processes.get(processName)
  436. if process is None:
  437. raise RPCError('CLEAR_BAD_NAME')
  438. try:
  439. # implies a reopen
  440. process.removelogs()
  441. except (IOError, os.error):
  442. msg = 'FATAL: failed clearing log file %r' % logfile
  443. raise RPCError('CLEAR_FAILED')
  444. return True
  445. def _rotateMainLog(self):
  446. """ Rotate the main supervisord log (for debugging/testing) """
  447. self._update('_rotateMainLog')
  448. for handler in self.supervisord.options.logger.handlers:
  449. if hasattr(handler, 'doRollover'):
  450. handler.doRollover()
  451. return True
  452. class SystemNamespaceRPCInterface:
  453. def __init__(self, namespaces):
  454. self.namespaces = {}
  455. for name, inst in namespaces:
  456. self.namespaces[name] = inst
  457. self.namespaces['system'] = self
  458. def _listMethods(self):
  459. methods = {}
  460. for ns_name in self.namespaces:
  461. namespace = self.namespaces[ns_name]
  462. for method_name in namespace.__class__.__dict__:
  463. # introspect; any methods that don't start with underscore
  464. # are published
  465. func = getattr(namespace, method_name)
  466. meth = getattr(func, 'im_func', None)
  467. if meth is not None:
  468. if not method_name.startswith('_'):
  469. sig = '%s.%s' % (ns_name, method_name)
  470. methods[sig] = str(func.__doc__)
  471. return methods
  472. def listMethods(self):
  473. """ Return an array listing the available method names
  474. @return array result An array of method names available (strings).
  475. """
  476. methods = self._listMethods()
  477. keys = methods.keys()
  478. keys.sort()
  479. return keys
  480. def methodHelp(self, name):
  481. """ Return a string showing the method's documentation
  482. @param string name The name of the method.
  483. @return string result The documentation for the method name.
  484. """
  485. methods = self._listMethods()
  486. for methodname in methods.keys():
  487. if methodname == name:
  488. return methods[methodname]
  489. raise RPCError('SIGNATURE_UNSUPPORTED')
  490. def methodSignature(self, name):
  491. """ Return an array describing the method signature in the
  492. form [rtype, ptype, ptype...] where rtype is the return data type
  493. of the method, and ptypes are the parameter data types that the
  494. method accepts in method argument order.
  495. @param string name The name of the method.
  496. @return array result The result.
  497. """
  498. methods = self._listMethods()
  499. L = []
  500. for method in methods:
  501. if method == name:
  502. rtype = None
  503. ptypes = []
  504. parsed = doctags.gettags(methods[method])
  505. for thing in parsed:
  506. if thing[1] == 'return': # tag name
  507. rtype = thing[2] # datatype
  508. elif thing[1] == 'param': # tag name
  509. ptypes.append(thing[2]) # datatype
  510. if rtype is None:
  511. raise RPCError('SIGNATURE_UNSUPPORTED')
  512. return [rtype] + ptypes
  513. raise RPCError('SIGNATURE_UNSUPPORTED')
  514. def multicall(self, calls):
  515. """Process an array of calls, and return an array of
  516. results. Calls should be structs of the form {'methodName':
  517. string, 'params': array}. Each result will either be a
  518. single-item array containg the result value, or a struct of
  519. the form {'faultCode': int, 'faultString': string}. This is
  520. useful when you need to make lots of small calls without lots
  521. of round trips.
  522. @param array calls An array of call requests
  523. @return array result An array of results
  524. """
  525. producers = []
  526. for call in calls:
  527. try:
  528. name = call['methodName']
  529. params = call.get('params', [])
  530. if name == 'system.multicall':
  531. # Recursive system.multicall forbidden
  532. error = 'INCORRECT_PARAMETERS'
  533. raise xmlrpclib.Fault(FAULTS[error], error)
  534. root = AttrDict(self.namespaces)
  535. value = traverse(root, name, params)
  536. except RPCError, inst:
  537. value = {'faultCode': inst.code,
  538. 'faultString': inst.text}
  539. except:
  540. errmsg = "%s:%s" % (sys.exc_type, sys.exc_value)
  541. value = {'faultCode': 1, 'faultString': errmsg}
  542. producers.append(value)
  543. results = []
  544. def multiproduce():
  545. """ Run through all the producers in order """
  546. if not producers:
  547. return []
  548. callback = producers.pop(0)
  549. if isinstance(callback, types.FunctionType):
  550. try:
  551. value = callback()
  552. except RPCError, inst:
  553. value = {'faultCode':inst.code, 'faultString':inst.text}
  554. if value is NOT_DONE_YET:
  555. # push it back in the front of the queue because we
  556. # need to finish the calls in requested order
  557. producers.insert(0, callback)
  558. return NOT_DONE_YET
  559. else:
  560. value = callback
  561. results.append(value)
  562. if producers:
  563. # only finish when all producers are finished
  564. return NOT_DONE_YET
  565. return results
  566. multiproduce.delay = .05
  567. return multiproduce
  568. class AttrDict(dict):
  569. # hack to make a dict's getattr equivalent to its getitem
  570. def __getattr__(self, name):
  571. return self[name]
  572. class RPCInterface:
  573. def __init__(self, supervisord):
  574. self.supervisord = supervisord
  575. self.supervisor = SupervisorNamespaceRPCInterface(supervisord)
  576. self.system = SystemNamespaceRPCInterface(
  577. [('supervisor', self.supervisor)]
  578. )
  579. class supervisor_xmlrpc_handler(xmlrpc_handler):
  580. def __init__(self, supervisord):
  581. self.rpcinterface = RPCInterface(supervisord)
  582. self.supervisord = supervisord
  583. def continue_request (self, data, request):
  584. logger = self.supervisord.options.logger
  585. try:
  586. params, method = xmlrpclib.loads(data)
  587. try:
  588. # 5 is 'trace' level
  589. logger.log(5, 'XML-RPC method called: %s()' % method)
  590. value = self.call(method, params)
  591. logger.log(5, 'XML-RPC method %s() returned successfully' %
  592. method)
  593. except RPCError, err:
  594. # turn RPCError reported by method into a Fault instance
  595. value = xmlrpclib.Fault(err.code, err.text)
  596. logger.warn('XML-RPC method %s() returned fault: [%d] %s' % (
  597. method,
  598. err.code, err.text))
  599. if isinstance(value, types.FunctionType):
  600. # returning a function from an RPC method implies that
  601. # this needs to be a deferred response (it needs to block).
  602. pushproducer = request.channel.push_with_producer
  603. pushproducer(DeferredXMLRPCResponse(request, value))
  604. else:
  605. # if we get anything but a function, it implies that this
  606. # response doesn't need to be deferred, we can service it
  607. # right away.
  608. body = xmlrpc_marshal(value)
  609. request['Content-Type'] = 'text/xml'
  610. request['Content-Length'] = len(body)
  611. request.push(body)
  612. request.done()
  613. except:
  614. io = StringIO.StringIO()
  615. traceback.print_exc(file=io)
  616. val = io.getvalue()
  617. logger.critical(val)
  618. # internal error, report as HTTP server error
  619. request.error(500)
  620. def call(self, method, params):
  621. return traverse(self.rpcinterface, method, params)
  622. def traverse(ob, method, params):
  623. path = method.split('.')
  624. for name in path:
  625. if name.startswith('_'):
  626. # security (don't allow things that start with an underscore to
  627. # be called remotely)
  628. raise RPCError('UNKNOWN_METHOD')
  629. ob = getattr(ob, name, None)
  630. if ob is None:
  631. raise RPCError('UNKNOWN_METHOD')
  632. try:
  633. return ob(*params)
  634. except TypeError:
  635. raise RPCError('INCORRECT_PARAMETERS')
  636. def _readFile(filename, offset, length):
  637. """ Read length bytes from the file named by filename starting at
  638. offset """
  639. absoffset = abs(offset)
  640. abslength = abs(length)
  641. try:
  642. f = open(filename, 'rb')
  643. if absoffset != offset:
  644. # negative offset returns offset bytes from tail of the file
  645. if length:
  646. raise ValueError('BAD_ARGUMENTS')
  647. f.seek(0, 2)
  648. sz = f.tell()
  649. pos = int(sz - absoffset)
  650. f.seek(pos)
  651. data = f.read(absoffset)
  652. else:
  653. if abslength != length:
  654. raise ValueError('BAD_ARGUMENTS')
  655. if length == 0:
  656. f.seek(offset)
  657. data = f.read()
  658. else:
  659. sz = f.seek(offset)
  660. data = f.read(length)
  661. except (os.error, IOError):
  662. raise ValueError('FAILED')
  663. return data