IF YOU WOULD LIKE TO GET AN ACCOUNT, please write an email to s dot adaszewski at gmail dot com. User accounts are meant only to report issues and/or generate pull requests. This is a purpose-specific Git hosting for ADARED projects. Thank you for your understanding!
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

test_compose.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. from focker.compose import exec_hook, \
  2. exec_prebuild, \
  3. exec_postbuild, \
  4. build_volumes, \
  5. build_images, \
  6. setup_dependencies, \
  7. build_jails, \
  8. stop_jails, \
  9. command_compose_build
  10. import focker.compose
  11. from tempfile import TemporaryDirectory
  12. import os
  13. import pytest
  14. import fcntl
  15. from focker.misc import focker_lock, \
  16. focker_unlock
  17. import inspect
  18. import ast
  19. import stat
  20. from focker.zfs import zfs_find, \
  21. zfs_mountpoint, \
  22. zfs_parse_output
  23. import subprocess
  24. import yaml
  25. import jailconf
  26. from focker.jail import backup_file
  27. from collections import defaultdict
  28. def test_exec_hook_01():
  29. spec = [
  30. 'touch test-exec-hook-01',
  31. 'touch test-exec-hook-02'
  32. ]
  33. with TemporaryDirectory() as d:
  34. exec_hook(spec, d, 'test-exec-hook')
  35. assert os.path.exists(os.path.join(d, 'test-exec-hook-01'))
  36. assert os.path.exists(os.path.join(d, 'test-exec-hook-02'))
  37. assert not os.path.exists(d)
  38. def test_exec_hook_02():
  39. spec = 'touch test-exec-hook-01 && touch test-exec-hook-02'
  40. with TemporaryDirectory() as d:
  41. exec_hook(spec, d, 'test-exec-hook')
  42. assert os.path.exists(os.path.join(d, 'test-exec-hook-01'))
  43. assert os.path.exists(os.path.join(d, 'test-exec-hook-02'))
  44. assert not os.path.exists(d)
  45. def test_exec_hook_03a():
  46. spec = 1
  47. with TemporaryDirectory() as d:
  48. with pytest.raises(ValueError):
  49. exec_hook(spec, d, 'test-exec-hook')
  50. def test_exec_hook_03b():
  51. spec = [1]
  52. with TemporaryDirectory() as d:
  53. with pytest.raises(TypeError):
  54. exec_hook(spec, d, 'test-exec-hook')
  55. def test_exec_hook_04():
  56. spec = 'ls'
  57. with pytest.raises(FileNotFoundError):
  58. exec_hook(spec, '/non-existent-directory/wcj20fy103', 'test-exec-hook')
  59. def test_exec_hook_05():
  60. spec = 'ls'
  61. oldwd = os.getcwd()
  62. with TemporaryDirectory() as d:
  63. exec_hook(spec, d, 'test-exec-hook')
  64. assert os.getcwd() == oldwd
  65. def test_exec_hook_06():
  66. spec = '/non-existent-command/hf249h'
  67. with TemporaryDirectory() as d:
  68. with pytest.raises(RuntimeError):
  69. exec_hook(spec, d, 'test-exec-hook')
  70. def test_exec_hook_07():
  71. os.chdir('/')
  72. spec = 'flock --nonblock /var/lock/focker.lock -c ls'
  73. focker_lock()
  74. assert fcntl.flock(focker_lock.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) != 0
  75. with TemporaryDirectory() as d:
  76. exec_hook(spec, d, 'test-exec-hook')
  77. assert fcntl.flock(focker_lock.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) != 0
  78. focker_unlock()
  79. def _test_simple_forward(fun, fwd_fun_name='exec_hook'):
  80. src = inspect.getsource(fun)
  81. mod = ast.parse(src)
  82. assert isinstance(mod.body[0], ast.FunctionDef)
  83. assert isinstance(mod.body[0].body[0], ast.Return)
  84. assert isinstance(mod.body[0].body[0].value, ast.Call)
  85. assert mod.body[0].body[0].value.func.id == fwd_fun_name
  86. def test_exec_prebuild():
  87. _test_simple_forward(exec_prebuild)
  88. def test_exec_postbuild():
  89. _test_simple_forward(exec_postbuild)
  90. def test_build_volumes():
  91. subprocess.check_output(['focker', 'volume', 'remove', '--force', 'test-build-volumes'])
  92. err = False
  93. try:
  94. name, _ = zfs_find('test-build-volumes', focker_type='volume')
  95. except:
  96. err = True
  97. assert err
  98. spec = {
  99. 'test-build-volumes': {
  100. 'chown': '65534:65534',
  101. 'chmod': 0o123,
  102. 'protect': True,
  103. 'zfs': {
  104. 'quota': '1G',
  105. 'readonly': 'on'
  106. }
  107. }
  108. }
  109. build_volumes(spec)
  110. name, _ = zfs_find('test-build-volumes', focker_type='volume')
  111. st = os.stat(zfs_mountpoint(name))
  112. assert st.st_uid == 65534
  113. assert st.st_gid == 65534
  114. assert ('%o' % st.st_mode)[-3:] == '123'
  115. zst = zfs_parse_output(['zfs', 'get', '-H', 'quota,readonly,focker:protect', name])
  116. assert zst[0][2] == '1G'
  117. assert zst[1][2] == 'on'
  118. assert zst[2][2] == 'on'
  119. subprocess.check_output(['zfs', 'destroy', '-r', '-f', name])
  120. def test_build_images():
  121. subprocess.check_output(['focker', 'image', 'remove', '--force', 'test-focker-bootstrap'])
  122. subprocess.check_output(['focker', 'bootstrap', '--empty', '--tags', 'test-focker-bootstrap'])
  123. subprocess.check_output(['focker', 'image', 'remove', '--force', 'test-build-images'])
  124. with TemporaryDirectory() as d:
  125. with open(os.path.join(d, 'Fockerfile'), 'w') as f:
  126. yaml.dump({
  127. 'base': 'test-focker-bootstrap',
  128. 'steps': [
  129. { 'copy': [
  130. [ '/bin/sh', '/bin/sh', { 'chmod': 0o777 } ],
  131. [ '/lib/libedit.so.7', '/lib/libedit.so.7' ],
  132. [ '/lib/libncursesw.so.8', '/lib/libncursesw.so.8' ],
  133. [ '/lib/libc.so.7', '/lib/libc.so.7' ],
  134. [ '/usr/bin/touch', '/usr/bin/touch', { 'chmod': 0o777 } ],
  135. [ '/libexec/ld-elf.so.1', '/libexec/ld-elf.so.1', { 'chmod': 0o555 } ]
  136. ] },
  137. { 'run': 'touch /test-build-images' }
  138. ]
  139. }, f)
  140. args = lambda: 0
  141. args.squeeze = False
  142. build_images({
  143. 'test-build-images': '.'
  144. }, d, args)
  145. focker_unlock()
  146. name, _ = zfs_find('test-build-images', focker_type='image')
  147. assert os.path.exists(os.path.join(zfs_mountpoint(name), 'test-build-images'))
  148. subprocess.check_output(['focker', 'image', 'remove', '--force', 'test-build-images'])
  149. subprocess.check_output(['focker', 'image', 'prune'])
  150. subprocess.check_output(['focker', 'image', 'remove', '--force', 'test-focker-bootstrap'])
  151. def test_setup_dependencies():
  152. backup_file('/etc/jail.conf')
  153. conf = jailconf.load('/etc/jail.conf')
  154. jail = jailconf.JailBlock()
  155. conf['test-setup-dependencies-A'] = jail
  156. conf['test-setup-dependencies-B'] = jail
  157. conf['test-setup-dependencies-C'] = jail
  158. conf.write('/etc/jail.conf')
  159. setup_dependencies({
  160. 'test-setup-dependencies-A': {},
  161. 'test-setup-dependencies-B': { 'depend': 'test-setup-dependencies-A' },
  162. 'test-setup-dependencies-C': { 'depend': [
  163. 'test-setup-dependencies-A',
  164. 'test-setup-dependencies-B'
  165. ] }
  166. }, {
  167. 'test-setup-dependencies-A': 'test-setup-dependencies-A',
  168. 'test-setup-dependencies-B': 'test-setup-dependencies-B',
  169. 'test-setup-dependencies-C': 'test-setup-dependencies-C'
  170. })
  171. conf = jailconf.load('/etc/jail.conf')
  172. assert 'depend' not in conf['test-setup-dependencies-A']
  173. assert conf['test-setup-dependencies-B']['depend'] == 'test-setup-dependencies-A'
  174. assert conf['test-setup-dependencies-C']['depend'] == [
  175. 'test-setup-dependencies-A',
  176. 'test-setup-dependencies-B'
  177. ]
  178. del conf['test-setup-dependencies-A']
  179. del conf['test-setup-dependencies-B']
  180. del conf['test-setup-dependencies-C']
  181. conf.write('/etc/jail.conf')
  182. def test_build_jails():
  183. backup_file('/etc/jail.conf')
  184. conf = jailconf.load('/etc/jail.conf')
  185. for k in list(conf.keys()):
  186. if conf[k]['host.hostname'].strip('\'"') in ['test-build-jails-A', 'test-build-jails-B']:
  187. del conf[k]
  188. conf.write('/etc/jail.conf')
  189. subprocess.check_output(['focker', 'jail', 'remove', '--force', 'test-build-jails-A'])
  190. subprocess.check_output(['focker', 'jail', 'remove', '--force', 'test-build-jails-B'])
  191. subprocess.check_output(['focker', 'image', 'remove', '--force', '-R', 'test-focker-bootstrap'])
  192. subprocess.check_output(['focker', 'bootstrap', '--empty', '-t', 'test-focker-bootstrap'])
  193. spec = {
  194. 'test-build-jails-A': {
  195. 'image': 'test-focker-bootstrap',
  196. 'exec.start': 'test-exec-start',
  197. 'exec.stop': 'test-exec-stop',
  198. 'ip4.addr': 'test-ip4-addr',
  199. 'interface': 'test-interface',
  200. 'host.hostname': 'test-build-jails-A',
  201. 'jail.conf': {
  202. 'allow.mount': True,
  203. 'ip6.addr': 'abcd:abcd::0'
  204. }
  205. }
  206. }
  207. spec['test-build-jails-B'] = spec['test-build-jails-A'].copy()
  208. spec['test-build-jails-B']['host.hostname'] = 'test-build-jails-B'
  209. build_jails(spec)
  210. conf = jailconf.load('/etc/jail.conf')
  211. print(conf.values())
  212. blocks = list(filter(lambda a: a['host.hostname'].strip('"\'') in [ 'test-build-jails-A',
  213. 'test-build-jails-B' ], conf.values()))
  214. print(blocks)
  215. assert len(blocks) == 2
  216. assert blocks[0]['host.hostname'] != blocks[1]['host.hostname']
  217. for b in blocks:
  218. name, _ = zfs_find(b['host.hostname'].strip('\'"'), focker_type='jail')
  219. mountpoint = zfs_mountpoint(name)
  220. assert b['path'] == mountpoint
  221. assert b['exec.start'].strip('\'"') == 'test-exec-start'
  222. assert b['exec.stop'].strip('\'"') == 'test-exec-stop'
  223. assert b['ip4.addr'].strip('\'"') == 'test-ip4-addr'
  224. assert b['interface'].strip('\'"') == 'test-interface'
  225. assert b['allow.mount']
  226. assert b['ip6.addr'] == '\'abcd:abcd::0\''
  227. subprocess.check_output(['focker', 'jail', 'remove', '--force', 'test-build-jails-A'])
  228. subprocess.check_output(['focker', 'jail', 'remove', '--force', 'test-build-jails-B'])
  229. subprocess.check_output(['focker', 'image', 'remove', '--force', 'test-focker-bootstrap'])
  230. for k in list(conf.keys()):
  231. if conf[k]['host.hostname'].strip('\'"') in ['test-build-jails-A', 'test-build-jails-B']:
  232. del conf[k]
  233. conf.write('/etc/jail.conf')
  234. def test_stop_jails_01(monkeypatch):
  235. spec = {
  236. 'foobar': {}
  237. }
  238. def mock_zfs_find(name, focker_type):
  239. assert name == 'foobar'
  240. assert focker_type == 'jail'
  241. return 'baf', None
  242. def mock_zfs_mountpoint(name):
  243. assert name == 'baf'
  244. return '/beef'
  245. def mock_jail_stop(path):
  246. assert path == '/beef'
  247. monkeypatch.setattr(focker.compose, 'zfs_find', mock_zfs_find)
  248. monkeypatch.setattr(focker.compose, 'zfs_mountpoint', mock_zfs_mountpoint)
  249. monkeypatch.setattr(focker.compose, 'jail_stop', mock_jail_stop)
  250. stop_jails(spec)
  251. def test_stop_jails_02(monkeypatch):
  252. spec = {
  253. 'foobar': {}
  254. }
  255. def mock_zfs_find(name, focker_type):
  256. assert name == 'foobar'
  257. assert focker_type == 'jail'
  258. raise ValueError('Not found')
  259. def mock_zfs_mountpoint(name):
  260. mock_zfs_mountpoint.called = True
  261. mock_zfs_mountpoint.called = False
  262. def mock_jail_stop(path):
  263. mock_jail_stop.called = True
  264. mock_jail_stop.called = False
  265. monkeypatch.setattr(focker.compose, 'zfs_find', mock_zfs_find)
  266. monkeypatch.setattr(focker.compose, 'zfs_mountpoint', mock_zfs_mountpoint)
  267. monkeypatch.setattr(focker.compose, 'jail_stop', mock_jail_stop)
  268. stop_jails(spec)
  269. assert not mock_zfs_mountpoint.called
  270. assert not mock_jail_stop.called
  271. def test_command_compose_build(monkeypatch):
  272. with TemporaryDirectory() as d:
  273. with open(os.path.join(d, 'focker-compose.yml'), 'w') as f:
  274. yaml.dump({
  275. 'exec.prebuild': 'echo exec-prebuild',
  276. 'volumes': {},
  277. 'images': {},
  278. 'jails': {},
  279. 'exec.postbuild': 'echo exec-postbuild'
  280. }, f)
  281. args = lambda: 0
  282. args.filename = os.path.join(d, 'focker-compose.yml')
  283. log = defaultdict(list)
  284. def log_calls(fun):
  285. old = fun.__call__
  286. def inner(*args, **kwargs):
  287. log[fun.__name__].append((args, kwargs))
  288. return old(*args, **kwargs)
  289. return inner
  290. monkeypatch.setattr(focker.compose, 'exec_prebuild', log_calls(exec_prebuild))
  291. monkeypatch.setattr(focker.compose, 'build_volumes', log_calls(build_volumes))
  292. monkeypatch.setattr(focker.compose, 'build_images', log_calls(build_images))
  293. monkeypatch.setattr(focker.compose, 'build_jails', log_calls(build_jails))
  294. monkeypatch.setattr(focker.compose, 'exec_postbuild', log_calls(exec_postbuild))
  295. command_compose_build(args)
  296. assert len(log) == 5
  297. assert 'exec_prebuild' in log
  298. assert 'build_volumes' in log
  299. assert 'build_images' in log
  300. assert 'build_jails' in log
  301. assert 'exec_postbuild' in log
  302. assert log['exec_prebuild'][0][0][0] == 'echo exec-prebuild'
  303. assert log['build_volumes'][0][0][0] == {}
  304. assert log['build_images'][0][0][0] == {}
  305. assert log['build_jails'][0][0][0] == {}
  306. assert log['exec_postbuild'][0][0][0] == 'echo exec-postbuild'