test_platforms.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. from __future__ import absolute_import
  2. from __future__ import with_statement
  3. import errno
  4. import os
  5. import resource
  6. import signal
  7. from mock import Mock, patch
  8. from celery import current_app
  9. from celery import platforms
  10. from celery.platforms import (
  11. get_fdmax,
  12. shellsplit,
  13. ignore_errno,
  14. set_process_title,
  15. signals,
  16. maybe_drop_privileges,
  17. setuid,
  18. setgid,
  19. seteuid,
  20. setegid,
  21. initgroups,
  22. parse_uid,
  23. parse_gid,
  24. detached,
  25. DaemonContext,
  26. create_pidlock,
  27. PIDFile,
  28. LockFailed,
  29. setgroups,
  30. _setgroups_hack
  31. )
  32. from celery.tests.utils import Case, WhateverIO, override_stdouts
  33. class test_ignore_errno(Case):
  34. def test_raises_EBADF(self):
  35. with ignore_errno('EBADF'):
  36. exc = OSError()
  37. exc.errno = errno.EBADF
  38. raise exc
  39. def test_otherwise(self):
  40. with self.assertRaises(OSError):
  41. with ignore_errno('EBADF'):
  42. exc = OSError()
  43. exc.errno = errno.ENOENT
  44. raise exc
  45. class test_shellsplit(Case):
  46. def test_split(self):
  47. self.assertEqual(shellsplit("the 'quick' brown fox"),
  48. ['the', 'quick', 'brown', 'fox'])
  49. class test_set_process_title(Case):
  50. def when_no_setps(self):
  51. prev = platforms._setproctitle = platforms._setproctitle, None
  52. try:
  53. set_process_title('foo')
  54. finally:
  55. platforms._setproctitle = prev
  56. class test_Signals(Case):
  57. @patch('signal.getsignal')
  58. def test_getitem(self, getsignal):
  59. signals['SIGINT']
  60. getsignal.assert_called_with(signal.SIGINT)
  61. def test_supported(self):
  62. self.assertTrue(signals.supported('INT'))
  63. self.assertFalse(signals.supported('SIGIMAGINARY'))
  64. def test_signum(self):
  65. self.assertEqual(signals.signum(13), 13)
  66. self.assertEqual(signals.signum('INT'), signal.SIGINT)
  67. self.assertEqual(signals.signum('SIGINT'), signal.SIGINT)
  68. with self.assertRaises(TypeError):
  69. signals.signum('int')
  70. signals.signum(object())
  71. @patch('signal.signal')
  72. def test_ignore(self, set):
  73. signals.ignore('SIGINT')
  74. set.assert_called_with(signals.signum('INT'), signals.ignored)
  75. signals.ignore('SIGTERM')
  76. set.assert_called_with(signals.signum('TERM'), signals.ignored)
  77. @patch('signal.signal')
  78. def test_setitem(self, set):
  79. handle = lambda *a: a
  80. signals['INT'] = handle
  81. set.assert_called_with(signal.SIGINT, handle)
  82. @patch('signal.signal')
  83. def test_setitem_raises(self, set):
  84. set.side_effect = ValueError()
  85. signals['INT'] = lambda *a: a
  86. if not current_app.IS_WINDOWS:
  87. class test_get_fdmax(Case):
  88. @patch('resource.getrlimit')
  89. def test_when_infinity(self, getrlimit):
  90. getrlimit.return_value = [None, resource.RLIM_INFINITY]
  91. default = object()
  92. self.assertIs(get_fdmax(default), default)
  93. @patch('resource.getrlimit')
  94. def test_when_actual(self, getrlimit):
  95. getrlimit.return_value = [None, 13]
  96. self.assertEqual(get_fdmax(None), 13)
  97. class test_maybe_drop_privileges(Case):
  98. @patch('celery.platforms.parse_uid')
  99. @patch('pwd.getpwuid')
  100. @patch('celery.platforms.setgid')
  101. @patch('celery.platforms.setuid')
  102. @patch('celery.platforms.initgroups')
  103. def test_with_uid(self, initgroups, setuid, setgid,
  104. getpwuid, parse_uid):
  105. class pw_struct(object):
  106. pw_gid = 50001
  107. getpwuid.return_value = pw_struct()
  108. parse_uid.return_value = 5001
  109. maybe_drop_privileges(uid='user')
  110. parse_uid.assert_called_with('user')
  111. getpwuid.assert_called_with(5001)
  112. setgid.assert_called_with(50001)
  113. initgroups.assert_called_with(5001, 50001)
  114. setuid.assert_called_with(5001)
  115. @patch('celery.platforms.parse_uid')
  116. @patch('celery.platforms.parse_gid')
  117. @patch('celery.platforms.setgid')
  118. @patch('celery.platforms.setuid')
  119. @patch('celery.platforms.initgroups')
  120. def test_with_guid(self, initgroups, setuid, setgid,
  121. parse_gid, parse_uid):
  122. parse_uid.return_value = 5001
  123. parse_gid.return_value = 50001
  124. maybe_drop_privileges(uid='user', gid='group')
  125. parse_uid.assert_called_with('user')
  126. parse_gid.assert_called_with('group')
  127. setgid.assert_called_with(50001)
  128. initgroups.assert_called_with(5001, 50001)
  129. setuid.assert_called_with(5001)
  130. @patch('celery.platforms.setuid')
  131. @patch('celery.platforms.setgid')
  132. @patch('celery.platforms.parse_gid')
  133. def test_only_gid(self, parse_gid, setgid, setuid):
  134. parse_gid.return_value = 50001
  135. maybe_drop_privileges(gid='group')
  136. parse_gid.assert_called_with('group')
  137. setgid.assert_called_with(50001)
  138. self.assertFalse(setuid.called)
  139. class test_setget_uid_gid(Case):
  140. @patch('celery.platforms.parse_uid')
  141. @patch('os.setuid')
  142. def test_setuid(self, _setuid, parse_uid):
  143. parse_uid.return_value = 5001
  144. setuid('user')
  145. parse_uid.assert_called_with('user')
  146. _setuid.assert_called_with(5001)
  147. @patch('celery.platforms.parse_uid')
  148. @patch('os.geteuid')
  149. @patch('os.seteuid')
  150. def test_seteuid(self, _seteuid, _geteuid, parse_uid):
  151. parse_uid.return_value = 5001
  152. _geteuid.return_value = 5001
  153. seteuid('user')
  154. parse_uid.assert_called_with('user')
  155. self.assertFalse(_seteuid.called)
  156. _geteuid.return_value = 1
  157. seteuid('user')
  158. _seteuid.assert_called_with(5001)
  159. @patch('celery.platforms.parse_gid')
  160. @patch('os.setgid')
  161. def test_setgid(self, _setgid, parse_gid):
  162. parse_gid.return_value = 50001
  163. setgid('group')
  164. parse_gid.assert_called_with('group')
  165. _setgid.assert_called_with(50001)
  166. @patch('celery.platforms.parse_gid')
  167. @patch('os.getegid')
  168. @patch('os.setegid')
  169. def test_setegid(self, _setegid, _getegid, parse_gid):
  170. parse_gid.return_value = 50001
  171. _getegid.return_value = 50001
  172. setegid('group')
  173. parse_gid.assert_called_with('group')
  174. self.assertFalse(_setegid.called)
  175. _getegid.return_value = 1
  176. setegid('group')
  177. _setegid.assert_called_with(50001)
  178. def test_parse_uid_when_int(self):
  179. self.assertEqual(parse_uid(5001), 5001)
  180. @patch('pwd.getpwnam')
  181. def test_parse_uid_when_existing_name(self, getpwnam):
  182. class pwent(object):
  183. pw_uid = 5001
  184. getpwnam.return_value = pwent()
  185. self.assertEqual(parse_uid('user'), 5001)
  186. @patch('pwd.getpwnam')
  187. def test_parse_uid_when_nonexisting_name(self, getpwnam):
  188. getpwnam.side_effect = KeyError('user')
  189. with self.assertRaises(KeyError):
  190. parse_uid('user')
  191. def test_parse_gid_when_int(self):
  192. self.assertEqual(parse_gid(50001), 50001)
  193. @patch('grp.getgrnam')
  194. def test_parse_gid_when_existing_name(self, getgrnam):
  195. class grent(object):
  196. gr_gid = 50001
  197. getgrnam.return_value = grent()
  198. self.assertEqual(parse_gid('group'), 50001)
  199. @patch('grp.getgrnam')
  200. def test_parse_gid_when_nonexisting_name(self, getgrnam):
  201. getgrnam.side_effect = KeyError('group')
  202. with self.assertRaises(KeyError):
  203. parse_gid('group')
  204. class test_initgroups(Case):
  205. @patch('pwd.getpwuid')
  206. @patch('os.initgroups', create=True)
  207. def test_with_initgroups(self, initgroups_, getpwuid):
  208. getpwuid.return_value = ['user']
  209. initgroups(5001, 50001)
  210. initgroups_.assert_called_with('user', 50001)
  211. @patch('celery.platforms.setgroups')
  212. @patch('grp.getgrall')
  213. @patch('pwd.getpwuid')
  214. def test_without_initgroups(self, getpwuid, getgrall, setgroups):
  215. prev = getattr(os, 'initgroups', None)
  216. try:
  217. delattr(os, 'initgroups')
  218. except AttributeError:
  219. pass
  220. try:
  221. getpwuid.return_value = ['user']
  222. class grent(object):
  223. gr_mem = ['user']
  224. def __init__(self, gid):
  225. self.gr_gid = gid
  226. getgrall.return_value = [grent(1), grent(2), grent(3)]
  227. initgroups(5001, 50001)
  228. setgroups.assert_called_with([1, 2, 3])
  229. finally:
  230. if prev:
  231. os.initgroups = prev
  232. class test_detached(Case):
  233. def test_without_resource(self):
  234. prev, platforms.resource = platforms.resource, None
  235. try:
  236. with self.assertRaises(RuntimeError):
  237. detached()
  238. finally:
  239. platforms.resource = prev
  240. @patch('celery.platforms._create_pidlock')
  241. @patch('celery.platforms.signals')
  242. @patch('celery.platforms.maybe_drop_privileges')
  243. @patch('os.geteuid')
  244. @patch('__builtin__.open')
  245. def test_default(self, open, geteuid, maybe_drop,
  246. signals, pidlock):
  247. geteuid.return_value = 0
  248. context = detached(uid='user', gid='group')
  249. self.assertIsInstance(context, DaemonContext)
  250. signals.reset.assert_called_with('SIGCLD')
  251. maybe_drop.assert_called_with(uid='user', gid='group')
  252. open.return_value = Mock()
  253. geteuid.return_value = 5001
  254. context = detached(uid='user', gid='group', logfile='/foo/bar')
  255. self.assertIsInstance(context, DaemonContext)
  256. open.assert_called_with('/foo/bar', 'a')
  257. open.return_value.close.assert_called_with()
  258. context = detached(pidfile='/foo/bar/pid')
  259. self.assertIsInstance(context, DaemonContext)
  260. pidlock.assert_called_with('/foo/bar/pid')
  261. class test_DaemonContext(Case):
  262. @patch('os.fork')
  263. @patch('os.setsid')
  264. @patch('os._exit')
  265. @patch('os.chdir')
  266. @patch('os.umask')
  267. @patch('os.close')
  268. @patch('os.open')
  269. @patch('os.dup2')
  270. def test_open(self, dup2, open, close, umask, chdir, _exit, setsid,
  271. fork):
  272. x = DaemonContext(workdir='/opt/workdir')
  273. fork.return_value = 0
  274. with x:
  275. self.assertTrue(x._is_open)
  276. with x:
  277. pass
  278. self.assertEqual(fork.call_count, 2)
  279. setsid.assert_called_with()
  280. self.assertFalse(_exit.called)
  281. chdir.assert_called_with(x.workdir)
  282. umask.assert_called_with(x.umask)
  283. self.assertTrue(dup2.called)
  284. fork.reset_mock()
  285. fork.return_value = 1
  286. x = DaemonContext(workdir='/opt/workdir')
  287. with x:
  288. pass
  289. self.assertEqual(fork.call_count, 1)
  290. _exit.assert_called_with(0)
  291. x = DaemonContext(workdir='/opt/workdir', fake=True)
  292. x._detach = Mock()
  293. with x:
  294. pass
  295. self.assertFalse(x._detach.called)
  296. class test_PIDFile(Case):
  297. @patch('celery.platforms.PIDFile')
  298. def test_create_pidlock(self, PIDFile):
  299. p = PIDFile.return_value = Mock()
  300. p.is_locked.return_value = True
  301. p.remove_if_stale.return_value = False
  302. with self.assertRaises(SystemExit):
  303. create_pidlock('/var/pid')
  304. p.remove_if_stale.return_value = True
  305. ret = create_pidlock('/var/pid')
  306. self.assertIs(ret, p)
  307. def test_context(self):
  308. p = PIDFile('/var/pid')
  309. p.write_pid = Mock()
  310. p.remove = Mock()
  311. with p as _p:
  312. self.assertIs(_p, p)
  313. p.write_pid.assert_called_with()
  314. p.remove.assert_called_with()
  315. def test_acquire_raises_LockFailed(self):
  316. p = PIDFile('/var/pid')
  317. p.write_pid = Mock()
  318. p.write_pid.side_effect = OSError()
  319. with self.assertRaises(LockFailed):
  320. with p:
  321. pass
  322. @patch('os.path.exists')
  323. def test_is_locked(self, exists):
  324. p = PIDFile('/var/pid')
  325. exists.return_value = True
  326. self.assertTrue(p.is_locked())
  327. exists.return_value = False
  328. self.assertFalse(p.is_locked())
  329. @patch('__builtin__.open')
  330. def test_read_pid(self, open_):
  331. s = open_.return_value = WhateverIO()
  332. s.write('1816\n')
  333. s.seek(0)
  334. p = PIDFile('/var/pid')
  335. self.assertEqual(p.read_pid(), 1816)
  336. @patch('__builtin__.open')
  337. def test_read_pid_partially_written(self, open_):
  338. s = open_.return_value = WhateverIO()
  339. s.write('1816')
  340. s.seek(0)
  341. p = PIDFile('/var/pid')
  342. with self.assertRaises(ValueError):
  343. p.read_pid()
  344. @patch('__builtin__.open')
  345. def test_read_pid_raises_ENOENT(self, open_):
  346. exc = IOError()
  347. exc.errno = errno.ENOENT
  348. open_.side_effect = exc
  349. p = PIDFile('/var/pid')
  350. self.assertIsNone(p.read_pid())
  351. @patch('__builtin__.open')
  352. def test_read_pid_raises_IOError(self, open_):
  353. exc = IOError()
  354. exc.errno = errno.EAGAIN
  355. open_.side_effect = exc
  356. p = PIDFile('/var/pid')
  357. with self.assertRaises(IOError):
  358. p.read_pid()
  359. @patch('__builtin__.open')
  360. def test_read_pid_bogus_pidfile(self, open_):
  361. s = open_.return_value = WhateverIO()
  362. s.write('eighteensixteen\n')
  363. s.seek(0)
  364. p = PIDFile('/var/pid')
  365. with self.assertRaises(ValueError):
  366. p.read_pid()
  367. @patch('os.unlink')
  368. def test_remove(self, unlink):
  369. unlink.return_value = True
  370. p = PIDFile('/var/pid')
  371. p.remove()
  372. unlink.assert_called_with(p.path)
  373. @patch('os.unlink')
  374. def test_remove_ENOENT(self, unlink):
  375. exc = OSError()
  376. exc.errno = errno.ENOENT
  377. unlink.side_effect = exc
  378. p = PIDFile('/var/pid')
  379. p.remove()
  380. unlink.assert_called_with(p.path)
  381. @patch('os.unlink')
  382. def test_remove_EACCES(self, unlink):
  383. exc = OSError()
  384. exc.errno = errno.EACCES
  385. unlink.side_effect = exc
  386. p = PIDFile('/var/pid')
  387. p.remove()
  388. unlink.assert_called_with(p.path)
  389. @patch('os.unlink')
  390. def test_remove_OSError(self, unlink):
  391. exc = OSError()
  392. exc.errno = errno.EAGAIN
  393. unlink.side_effect = exc
  394. p = PIDFile('/var/pid')
  395. with self.assertRaises(OSError):
  396. p.remove()
  397. unlink.assert_called_with(p.path)
  398. @patch('os.kill')
  399. def test_remove_if_stale_process_alive(self, kill):
  400. p = PIDFile('/var/pid')
  401. p.read_pid = Mock()
  402. p.read_pid.return_value = 1816
  403. kill.return_value = 0
  404. self.assertFalse(p.remove_if_stale())
  405. kill.assert_called_with(1816, 0)
  406. p.read_pid.assert_called_with()
  407. kill.side_effect = OSError()
  408. kill.side_effect.errno = errno.ENOENT
  409. self.assertFalse(p.remove_if_stale())
  410. @patch('os.kill')
  411. def test_remove_if_stale_process_dead(self, kill):
  412. with override_stdouts():
  413. p = PIDFile('/var/pid')
  414. p.read_pid = Mock()
  415. p.read_pid.return_value = 1816
  416. p.remove = Mock()
  417. exc = OSError()
  418. exc.errno = errno.ESRCH
  419. kill.side_effect = exc
  420. self.assertTrue(p.remove_if_stale())
  421. kill.assert_called_with(1816, 0)
  422. p.remove.assert_called_with()
  423. def test_remove_if_stale_broken_pid(self):
  424. with override_stdouts():
  425. p = PIDFile('/var/pid')
  426. p.read_pid = Mock()
  427. p.read_pid.side_effect = ValueError()
  428. p.remove = Mock()
  429. self.assertTrue(p.remove_if_stale())
  430. p.remove.assert_called_with()
  431. def test_remove_if_stale_no_pidfile(self):
  432. p = PIDFile('/var/pid')
  433. p.read_pid = Mock()
  434. p.read_pid.return_value = None
  435. p.remove = Mock()
  436. self.assertTrue(p.remove_if_stale())
  437. p.remove.assert_called_with()
  438. @patch('os.fsync')
  439. @patch('os.getpid')
  440. @patch('os.open')
  441. @patch('os.fdopen')
  442. @patch('__builtin__.open')
  443. def test_write_pid(self, open_, fdopen, osopen, getpid, fsync):
  444. getpid.return_value = 1816
  445. osopen.return_value = 13
  446. w = fdopen.return_value = WhateverIO()
  447. w.close = Mock()
  448. r = open_.return_value = WhateverIO()
  449. r.write('1816\n')
  450. r.seek(0)
  451. p = PIDFile('/var/pid')
  452. p.write_pid()
  453. w.seek(0)
  454. self.assertEqual(w.readline(), '1816\n')
  455. self.assertTrue(w.close.called)
  456. getpid.assert_called_with()
  457. osopen.assert_called_with(p.path, platforms.PIDFILE_FLAGS,
  458. platforms.PIDFILE_MODE)
  459. fdopen.assert_called_with(13, 'w')
  460. fsync.assert_called_with(13)
  461. open_.assert_called_with(p.path)
  462. @patch('os.fsync')
  463. @patch('os.getpid')
  464. @patch('os.open')
  465. @patch('os.fdopen')
  466. @patch('__builtin__.open')
  467. def test_write_reread_fails(self, open_, fdopen,
  468. osopen, getpid, fsync):
  469. getpid.return_value = 1816
  470. osopen.return_value = 13
  471. w = fdopen.return_value = WhateverIO()
  472. w.close = Mock()
  473. r = open_.return_value = WhateverIO()
  474. r.write('11816\n')
  475. r.seek(0)
  476. p = PIDFile('/var/pid')
  477. with self.assertRaises(LockFailed):
  478. p.write_pid()
  479. class test_setgroups(Case):
  480. @patch('os.setgroups', create=True)
  481. def test_setgroups_hack_ValueError(self, setgroups):
  482. def on_setgroups(groups):
  483. if len(groups) <= 200:
  484. setgroups.return_value = True
  485. return
  486. raise ValueError()
  487. setgroups.side_effect = on_setgroups
  488. _setgroups_hack(range(400))
  489. setgroups.side_effect = ValueError()
  490. with self.assertRaises(ValueError):
  491. _setgroups_hack(range(400))
  492. @patch('os.setgroups', create=True)
  493. def test_setgroups_hack_OSError(self, setgroups):
  494. exc = OSError()
  495. exc.errno = errno.EINVAL
  496. def on_setgroups(groups):
  497. if len(groups) <= 200:
  498. setgroups.return_value = True
  499. return
  500. raise exc
  501. setgroups.side_effect = on_setgroups
  502. _setgroups_hack(range(400))
  503. setgroups.side_effect = exc
  504. with self.assertRaises(OSError):
  505. _setgroups_hack(range(400))
  506. exc2 = OSError()
  507. exc.errno = errno.ESRCH
  508. setgroups.side_effect = exc2
  509. with self.assertRaises(OSError):
  510. _setgroups_hack(range(400))
  511. @patch('os.sysconf')
  512. @patch('celery.platforms._setgroups_hack')
  513. def test_setgroups(self, hack, sysconf):
  514. sysconf.return_value = 100
  515. setgroups(range(400))
  516. hack.assert_called_with(range(100))
  517. @patch('os.sysconf')
  518. @patch('celery.platforms._setgroups_hack')
  519. def test_setgroups_sysconf_raises(self, hack, sysconf):
  520. sysconf.side_effect = ValueError()
  521. setgroups(range(400))
  522. hack.assert_called_with(range(400))
  523. @patch('os.getgroups')
  524. @patch('os.sysconf')
  525. @patch('celery.platforms._setgroups_hack')
  526. def test_setgroups_raises_ESRCH(self, hack, sysconf, getgroups):
  527. sysconf.side_effect = ValueError()
  528. esrch = OSError()
  529. esrch.errno = errno.ESRCH
  530. hack.side_effect = esrch
  531. with self.assertRaises(OSError):
  532. setgroups(range(400))
  533. @patch('os.getgroups')
  534. @patch('os.sysconf')
  535. @patch('celery.platforms._setgroups_hack')
  536. def test_setgroups_raises_EPERM(self, hack, sysconf, getgroups):
  537. sysconf.side_effect = ValueError()
  538. eperm = OSError()
  539. eperm.errno = errno.EPERM
  540. hack.side_effect = eperm
  541. getgroups.return_value = range(400)
  542. setgroups(range(400))
  543. getgroups.assert_called_with()
  544. getgroups.return_value = [1000]
  545. with self.assertRaises(OSError):
  546. setgroups(range(400))
  547. getgroups.assert_called_with()