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!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_compose.py 11KB

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