test_http.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. import base64
  2. import os
  3. import socket
  4. import stat
  5. import sys
  6. import tempfile
  7. import unittest
  8. try:
  9. from hashlib import sha1
  10. except ImportError:
  11. from sha import new as sha1
  12. from supervisor.tests.base import DummySupervisor
  13. from supervisor.tests.base import PopulatedDummySupervisor
  14. from supervisor.tests.base import DummyRPCInterfaceFactory
  15. from supervisor.tests.base import DummyPConfig
  16. from supervisor.tests.base import DummyOptions
  17. from supervisor.tests.base import DummyRequest
  18. from supervisor.http import NOT_DONE_YET
  19. class HandlerTests:
  20. def _makeOne(self, supervisord):
  21. return self._getTargetClass()(supervisord)
  22. def test_match(self):
  23. class FakeRequest:
  24. def __init__(self, uri):
  25. self.uri = uri
  26. supervisor = DummySupervisor()
  27. handler = self._makeOne(supervisor)
  28. self.assertEqual(handler.match(FakeRequest(handler.path)), True)
  29. class LogtailHandlerTests(HandlerTests, unittest.TestCase):
  30. def _getTargetClass(self):
  31. from supervisor.http import logtail_handler
  32. return logtail_handler
  33. def test_handle_request_stdout_logfile_none(self):
  34. options = DummyOptions()
  35. pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,
  36. stdout_logfile='/tmp/process1.log')
  37. supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)
  38. handler = self._makeOne(supervisord)
  39. request = DummyRequest('/logtail/process1', None, None, None)
  40. handler.handle_request(request)
  41. self.assertEqual(request._error, 410)
  42. def test_handle_request_stdout_logfile_missing(self):
  43. options = DummyOptions()
  44. pconfig = DummyPConfig(options, 'foo', 'foo', 'it/is/missing')
  45. supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)
  46. handler = self._makeOne(supervisord)
  47. request = DummyRequest('/logtail/foo', None, None, None)
  48. handler.handle_request(request)
  49. self.assertEqual(request._error, 410)
  50. def test_handle_request(self):
  51. f = tempfile.NamedTemporaryFile()
  52. t = f.name
  53. options = DummyOptions()
  54. pconfig = DummyPConfig(options, 'foo', 'foo', stdout_logfile=t)
  55. supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)
  56. handler = self._makeOne(supervisord)
  57. request = DummyRequest('/logtail/foo', None, None, None)
  58. handler.handle_request(request)
  59. self.assertEqual(request._error, None)
  60. from supervisor.medusa import http_date
  61. self.assertEqual(request.headers['Last-Modified'],
  62. http_date.build_http_date(os.stat(t)[stat.ST_MTIME]))
  63. self.assertEqual(request.headers['Content-Type'], 'text/plain')
  64. self.assertEqual(len(request.producers), 1)
  65. self.assertEqual(request._done, True)
  66. class MainLogTailHandlerTests(HandlerTests, unittest.TestCase):
  67. def _getTargetClass(self):
  68. from supervisor.http import mainlogtail_handler
  69. return mainlogtail_handler
  70. def test_handle_request_stdout_logfile_none(self):
  71. supervisor = DummySupervisor()
  72. handler = self._makeOne(supervisor)
  73. request = DummyRequest('/mainlogtail', None, None, None)
  74. handler.handle_request(request)
  75. self.assertEqual(request._error, 410)
  76. def test_handle_request_stdout_logfile_missing(self):
  77. supervisor = DummySupervisor()
  78. supervisor.options.logfile = '/not/there'
  79. request = DummyRequest('/mainlogtail', None, None, None)
  80. handler = self._makeOne(supervisor)
  81. handler.handle_request(request)
  82. self.assertEqual(request._error, 410)
  83. def test_handle_request(self):
  84. supervisor = DummySupervisor()
  85. f = tempfile.NamedTemporaryFile()
  86. t = f.name
  87. supervisor.options.logfile = t
  88. handler = self._makeOne(supervisor)
  89. request = DummyRequest('/mainlogtail', None, None, None)
  90. handler.handle_request(request)
  91. self.assertEqual(request._error, None)
  92. from supervisor.medusa import http_date
  93. self.assertEqual(request.headers['Last-Modified'],
  94. http_date.build_http_date(os.stat(t)[stat.ST_MTIME]))
  95. self.assertEqual(request.headers['Content-Type'], 'text/plain')
  96. self.assertEqual(len(request.producers), 1)
  97. self.assertEqual(request._done, True)
  98. class TailFProducerTests(unittest.TestCase):
  99. def _getTargetClass(self):
  100. from supervisor.http import tail_f_producer
  101. return tail_f_producer
  102. def _makeOne(self, request, filename, head):
  103. return self._getTargetClass()(request, filename, head)
  104. def test_handle_more(self):
  105. request = DummyRequest('/logtail/foo', None, None, None)
  106. from supervisor import http
  107. f = tempfile.NamedTemporaryFile()
  108. f.write('a' * 80)
  109. f.flush()
  110. producer = self._makeOne(request, f.name, 80)
  111. result = producer.more()
  112. self.assertEqual(result, 'a' * 80)
  113. f.write('w' * 100)
  114. f.flush()
  115. result = producer.more()
  116. self.assertEqual(result, 'w' * 100)
  117. result = producer.more()
  118. self.assertEqual(result, http.NOT_DONE_YET)
  119. f.truncate(0)
  120. f.flush()
  121. result = producer.more()
  122. self.assertEqual(result, '==> File truncated <==\n')
  123. def test_handle_more_follow(self):
  124. request = DummyRequest('/logtail/foo', None, None, None)
  125. f = tempfile.NamedTemporaryFile()
  126. f.write('a' * 80)
  127. f.flush()
  128. producer = self._makeOne(request, f.name, 80)
  129. result = producer.more()
  130. self.assertEqual(result, 'a' * 80)
  131. f.close()
  132. f2 = open(f.name, 'w')
  133. try:
  134. f2.write('b' * 80)
  135. f2.close()
  136. result = producer.more()
  137. finally:
  138. os.unlink(f2.name)
  139. self.assertEqual(result, 'b' * 80)
  140. class DeferringChunkedProducerTests(unittest.TestCase):
  141. def _getTargetClass(self):
  142. from supervisor.http import deferring_chunked_producer
  143. return deferring_chunked_producer
  144. def _makeOne(self, producer, footers=None):
  145. return self._getTargetClass()(producer, footers)
  146. def test_more_not_done_yet(self):
  147. wrapped = DummyProducer(NOT_DONE_YET)
  148. producer = self._makeOne(wrapped)
  149. self.assertEqual(producer.more(), NOT_DONE_YET)
  150. def test_more_string(self):
  151. wrapped = DummyProducer('hello')
  152. producer = self._makeOne(wrapped)
  153. self.assertEqual(producer.more(), '5\r\nhello\r\n')
  154. def test_more_nodata(self):
  155. wrapped = DummyProducer()
  156. producer = self._makeOne(wrapped, footers=['a', 'b'])
  157. self.assertEqual(producer.more(), '0\r\na\r\nb\r\n\r\n')
  158. class DeferringCompositeProducerTests(unittest.TestCase):
  159. def _getTargetClass(self):
  160. from supervisor.http import deferring_composite_producer
  161. return deferring_composite_producer
  162. def _makeOne(self, producers):
  163. return self._getTargetClass()(producers)
  164. def test_more_not_done_yet(self):
  165. wrapped = DummyProducer(NOT_DONE_YET)
  166. producer = self._makeOne([wrapped])
  167. self.assertEqual(producer.more(), NOT_DONE_YET)
  168. def test_more_string(self):
  169. wrapped1 = DummyProducer('hello')
  170. wrapped2 = DummyProducer('goodbye')
  171. producer = self._makeOne([wrapped1, wrapped2])
  172. self.assertEqual(producer.more(), 'hello')
  173. self.assertEqual(producer.more(), 'goodbye')
  174. self.assertEqual(producer.more(), '')
  175. def test_more_nodata(self):
  176. wrapped = DummyProducer()
  177. producer = self._makeOne([wrapped])
  178. self.assertEqual(producer.more(), '')
  179. class DeferringGlobbingProducerTests(unittest.TestCase):
  180. def _getTargetClass(self):
  181. from supervisor.http import deferring_globbing_producer
  182. return deferring_globbing_producer
  183. def _makeOne(self, producer, buffer_size=1<<16):
  184. return self._getTargetClass()(producer, buffer_size)
  185. def test_more_not_done_yet(self):
  186. wrapped = DummyProducer(NOT_DONE_YET)
  187. producer = self._makeOne(wrapped)
  188. self.assertEqual(producer.more(), NOT_DONE_YET)
  189. def test_more_string(self):
  190. wrapped = DummyProducer('hello', 'there', 'guy')
  191. producer = self._makeOne(wrapped, buffer_size=1)
  192. self.assertEqual(producer.more(), 'hello')
  193. wrapped = DummyProducer('hello', 'there', 'guy')
  194. producer = self._makeOne(wrapped, buffer_size=50)
  195. self.assertEqual(producer.more(), 'hellothereguy')
  196. def test_more_nodata(self):
  197. wrapped = DummyProducer()
  198. producer = self._makeOne(wrapped)
  199. self.assertEqual(producer.more(), '')
  200. class DeferringHookedProducerTests(unittest.TestCase):
  201. def _getTargetClass(self):
  202. from supervisor.http import deferring_hooked_producer
  203. return deferring_hooked_producer
  204. def _makeOne(self, producer, function):
  205. return self._getTargetClass()(producer, function)
  206. def test_more_not_done_yet(self):
  207. wrapped = DummyProducer(NOT_DONE_YET)
  208. producer = self._makeOne(wrapped, None)
  209. self.assertEqual(producer.more(), NOT_DONE_YET)
  210. def test_more_string(self):
  211. wrapped = DummyProducer('hello')
  212. L = []
  213. def callback(bytes):
  214. L.append(bytes)
  215. producer = self._makeOne(wrapped, callback)
  216. self.assertEqual(producer.more(), 'hello')
  217. self.assertEqual(L, [])
  218. producer.more()
  219. self.assertEqual(L, [5])
  220. def test_more_nodata(self):
  221. wrapped = DummyProducer()
  222. L = []
  223. def callback(bytes):
  224. L.append(bytes)
  225. producer = self._makeOne(wrapped, callback)
  226. self.assertEqual(producer.more(), '')
  227. self.assertEqual(L, [0])
  228. class EncryptedDictionaryAuthorizedTests(unittest.TestCase):
  229. def _getTargetClass(self):
  230. from supervisor.http import encrypted_dictionary_authorizer
  231. return encrypted_dictionary_authorizer
  232. def _makeOne(self, dict):
  233. return self._getTargetClass()(dict)
  234. def test_authorize_baduser(self):
  235. authorizer = self._makeOne({})
  236. self.assertFalse(authorizer.authorize(('foo', 'bar')))
  237. def test_authorize_gooduser_badpassword(self):
  238. authorizer = self._makeOne({'foo':'password'})
  239. self.assertFalse(authorizer.authorize(('foo', 'bar')))
  240. def test_authorize_gooduser_goodpassword(self):
  241. authorizer = self._makeOne({'foo':'password'})
  242. self.assertTrue(authorizer.authorize(('foo', 'password')))
  243. def test_authorize_gooduser_goodpassword_with_colon(self):
  244. authorizer = self._makeOne({'foo':'pass:word'})
  245. self.assertTrue(authorizer.authorize(('foo', 'pass:word')))
  246. def test_authorize_gooduser_badpassword_sha(self):
  247. password = '{SHA}' + sha1('password').hexdigest()
  248. authorizer = self._makeOne({'foo':password})
  249. self.assertFalse(authorizer.authorize(('foo', 'bar')))
  250. def test_authorize_gooduser_goodpassword_sha(self):
  251. password = '{SHA}' + sha1('password').hexdigest()
  252. authorizer = self._makeOne({'foo':password})
  253. self.assertTrue(authorizer.authorize(('foo', 'password')))
  254. class SupervisorAuthHandlerTests(unittest.TestCase):
  255. def _getTargetClass(self):
  256. from supervisor.http import supervisor_auth_handler
  257. return supervisor_auth_handler
  258. def _makeOne(self, dict, handler):
  259. return self._getTargetClass()(dict, handler)
  260. def test_ctor(self):
  261. handler = self._makeOne({'a':1}, None)
  262. from supervisor.http import encrypted_dictionary_authorizer
  263. self.assertEqual(handler.authorizer.__class__,
  264. encrypted_dictionary_authorizer)
  265. def test_handle_request_authorizes_good_credentials(self):
  266. request = DummyRequest('/logtail/process1', None, None, None)
  267. encoded = base64.b64encode("user:password")
  268. request.header = ["Authorization: Basic %s" % encoded]
  269. handler = DummyHandler()
  270. auth_handler = self._makeOne({'user':'password'}, handler)
  271. auth_handler.handle_request(request)
  272. self.assertTrue(handler.handled_request)
  273. def test_handle_request_authorizes_good_password_with_colon(self):
  274. request = DummyRequest('/logtail/process1', None, None, None)
  275. encoded = base64.b64encode("user:pass:word") # password contains colon
  276. request.header = ["Authorization: Basic %s" % encoded]
  277. handler = DummyHandler()
  278. auth_handler = self._makeOne({'user':'pass:word'}, handler)
  279. auth_handler.handle_request(request)
  280. self.assertTrue(handler.handled_request)
  281. def test_handle_request_does_not_authorize_bad_credentials(self):
  282. request = DummyRequest('/logtail/process1', None, None, None)
  283. encoded = base64.b64encode("wrong:wrong")
  284. request.header = ["Authorization: Basic %s" % encoded]
  285. handler = DummyHandler()
  286. auth_handler = self._makeOne({'user':'password'}, handler)
  287. auth_handler.handle_request(request)
  288. self.assertFalse(handler.handled_request)
  289. class TopLevelFunctionTests(unittest.TestCase):
  290. def _make_http_servers(self, sconfigs):
  291. options = DummyOptions()
  292. options.server_configs = sconfigs
  293. options.rpcinterface_factories = [('dummy',DummyRPCInterfaceFactory,{})]
  294. supervisord = DummySupervisor()
  295. from supervisor.http import make_http_servers
  296. servers = make_http_servers(options, supervisord)
  297. try:
  298. for config, s in servers:
  299. s.close()
  300. socketfile = config.get('file')
  301. if socketfile is not None:
  302. os.unlink(socketfile)
  303. finally:
  304. from asyncore import socket_map
  305. socket_map.clear()
  306. return servers
  307. def test_make_http_servers_noauth(self):
  308. socketfile = tempfile.mktemp()
  309. inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17735,
  310. 'username':None, 'password':None, 'section':'inet_http_server'}
  311. unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':0700,
  312. 'chown':(-1, -1), 'username':None, 'password':None,
  313. 'section':'unix_http_server'}
  314. servers = self._make_http_servers([inet, unix])
  315. self.assertEqual(len(servers), 2)
  316. inetdata = servers[0]
  317. self.assertEqual(inetdata[0], inet)
  318. server = inetdata[1]
  319. idents = [
  320. 'Supervisor XML-RPC Handler',
  321. 'Logtail HTTP Request Handler',
  322. 'Main Logtail HTTP Request Handler',
  323. 'Supervisor Web UI HTTP Request Handler',
  324. 'Default HTTP Request Handler'
  325. ]
  326. self.assertEqual([x.IDENT for x in server.handlers], idents)
  327. unixdata = servers[1]
  328. self.assertEqual(unixdata[0], unix)
  329. server = unixdata[1]
  330. self.assertEqual([x.IDENT for x in server.handlers], idents)
  331. def test_make_http_servers_withauth(self):
  332. socketfile = tempfile.mktemp()
  333. inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17736,
  334. 'username':'username', 'password':'password',
  335. 'section':'inet_http_server'}
  336. unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':0700,
  337. 'chown':(-1, -1), 'username':'username', 'password':'password',
  338. 'section':'unix_http_server'}
  339. servers = self._make_http_servers([inet, unix])
  340. self.assertEqual(len(servers), 2)
  341. from supervisor.http import supervisor_auth_handler
  342. for config, server in servers:
  343. for handler in server.handlers:
  344. self.assertTrue(isinstance(handler, supervisor_auth_handler),
  345. handler)
  346. class DummyHandler:
  347. def __init__(self):
  348. self.handled_request = False
  349. def handle_request(self, request):
  350. self.handled_request = True
  351. class DummyProducer:
  352. def __init__(self, *data):
  353. self.data = list(data)
  354. def more(self):
  355. if self.data:
  356. return self.data.pop(0)
  357. else:
  358. return ''
  359. def test_suite():
  360. return unittest.findTestCases(sys.modules[__name__])
  361. if __name__ == '__main__':
  362. unittest.main(defaultTest='test_suite')