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.

344 lines
13KB

  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'