project_test.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import random
  4. import py
  5. import pytest
  6. from docker.errors import NotFound
  7. from .. import mock
  8. from ..helpers import build_config
  9. from .testcases import DockerClientTestCase
  10. from compose.config import config
  11. from compose.config import ConfigurationError
  12. from compose.config.config import V2_0
  13. from compose.config.config import V2_1
  14. from compose.config.types import VolumeFromSpec
  15. from compose.config.types import VolumeSpec
  16. from compose.const import LABEL_PROJECT
  17. from compose.const import LABEL_SERVICE
  18. from compose.container import Container
  19. from compose.errors import HealthCheckFailed
  20. from compose.errors import NoHealthCheckConfigured
  21. from compose.project import Project
  22. from compose.project import ProjectError
  23. from compose.service import ConvergenceStrategy
  24. from tests.integration.testcases import v2_1_only
  25. from tests.integration.testcases import v2_only
  26. class ProjectTest(DockerClientTestCase):
  27. def test_containers(self):
  28. web = self.create_service('web')
  29. db = self.create_service('db')
  30. project = Project('composetest', [web, db], self.client)
  31. project.up()
  32. containers = project.containers()
  33. self.assertEqual(len(containers), 2)
  34. def test_containers_with_service_names(self):
  35. web = self.create_service('web')
  36. db = self.create_service('db')
  37. project = Project('composetest', [web, db], self.client)
  38. project.up()
  39. containers = project.containers(['web'])
  40. self.assertEqual(
  41. [c.name for c in containers],
  42. ['composetest_web_1'])
  43. def test_containers_with_extra_service(self):
  44. web = self.create_service('web')
  45. web_1 = web.create_container()
  46. db = self.create_service('db')
  47. db_1 = db.create_container()
  48. self.create_service('extra').create_container()
  49. project = Project('composetest', [web, db], self.client)
  50. self.assertEqual(
  51. set(project.containers(stopped=True)),
  52. set([web_1, db_1]),
  53. )
  54. def test_volumes_from_service(self):
  55. project = Project.from_config(
  56. name='composetest',
  57. config_data=build_config({
  58. 'data': {
  59. 'image': 'busybox:latest',
  60. 'volumes': ['/var/data'],
  61. },
  62. 'db': {
  63. 'image': 'busybox:latest',
  64. 'volumes_from': ['data'],
  65. },
  66. }),
  67. client=self.client,
  68. )
  69. db = project.get_service('db')
  70. data = project.get_service('data')
  71. self.assertEqual(db.volumes_from, [VolumeFromSpec(data, 'rw', 'service')])
  72. def test_volumes_from_container(self):
  73. data_container = Container.create(
  74. self.client,
  75. image='busybox:latest',
  76. volumes=['/var/data'],
  77. name='composetest_data_container',
  78. labels={LABEL_PROJECT: 'composetest'},
  79. )
  80. project = Project.from_config(
  81. name='composetest',
  82. config_data=build_config({
  83. 'db': {
  84. 'image': 'busybox:latest',
  85. 'volumes_from': ['composetest_data_container'],
  86. },
  87. }),
  88. client=self.client,
  89. )
  90. db = project.get_service('db')
  91. self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw'])
  92. @v2_only()
  93. def test_network_mode_from_service(self):
  94. project = Project.from_config(
  95. name='composetest',
  96. client=self.client,
  97. config_data=build_config({
  98. 'version': V2_0,
  99. 'services': {
  100. 'net': {
  101. 'image': 'busybox:latest',
  102. 'command': ["top"]
  103. },
  104. 'web': {
  105. 'image': 'busybox:latest',
  106. 'network_mode': 'service:net',
  107. 'command': ["top"]
  108. },
  109. },
  110. }),
  111. )
  112. project.up()
  113. web = project.get_service('web')
  114. net = project.get_service('net')
  115. self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
  116. @v2_only()
  117. def test_network_mode_from_container(self):
  118. def get_project():
  119. return Project.from_config(
  120. name='composetest',
  121. config_data=build_config({
  122. 'version': V2_0,
  123. 'services': {
  124. 'web': {
  125. 'image': 'busybox:latest',
  126. 'network_mode': 'container:composetest_net_container'
  127. },
  128. },
  129. }),
  130. client=self.client,
  131. )
  132. with pytest.raises(ConfigurationError) as excinfo:
  133. get_project()
  134. assert "container 'composetest_net_container' which does not exist" in excinfo.exconly()
  135. net_container = Container.create(
  136. self.client,
  137. image='busybox:latest',
  138. name='composetest_net_container',
  139. command='top',
  140. labels={LABEL_PROJECT: 'composetest'},
  141. )
  142. net_container.start()
  143. project = get_project()
  144. project.up()
  145. web = project.get_service('web')
  146. self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
  147. def test_net_from_service_v1(self):
  148. project = Project.from_config(
  149. name='composetest',
  150. config_data=build_config({
  151. 'net': {
  152. 'image': 'busybox:latest',
  153. 'command': ["top"]
  154. },
  155. 'web': {
  156. 'image': 'busybox:latest',
  157. 'net': 'container:net',
  158. 'command': ["top"]
  159. },
  160. }),
  161. client=self.client,
  162. )
  163. project.up()
  164. web = project.get_service('web')
  165. net = project.get_service('net')
  166. self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
  167. def test_net_from_container_v1(self):
  168. def get_project():
  169. return Project.from_config(
  170. name='composetest',
  171. config_data=build_config({
  172. 'web': {
  173. 'image': 'busybox:latest',
  174. 'net': 'container:composetest_net_container'
  175. },
  176. }),
  177. client=self.client,
  178. )
  179. with pytest.raises(ConfigurationError) as excinfo:
  180. get_project()
  181. assert "container 'composetest_net_container' which does not exist" in excinfo.exconly()
  182. net_container = Container.create(
  183. self.client,
  184. image='busybox:latest',
  185. name='composetest_net_container',
  186. command='top',
  187. labels={LABEL_PROJECT: 'composetest'},
  188. )
  189. net_container.start()
  190. project = get_project()
  191. project.up()
  192. web = project.get_service('web')
  193. self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
  194. def test_start_pause_unpause_stop_kill_remove(self):
  195. web = self.create_service('web')
  196. db = self.create_service('db')
  197. project = Project('composetest', [web, db], self.client)
  198. project.start()
  199. self.assertEqual(len(web.containers()), 0)
  200. self.assertEqual(len(db.containers()), 0)
  201. web_container_1 = web.create_container()
  202. web_container_2 = web.create_container()
  203. db_container = db.create_container()
  204. project.start(service_names=['web'])
  205. self.assertEqual(
  206. set(c.name for c in project.containers()),
  207. set([web_container_1.name, web_container_2.name]))
  208. project.start()
  209. self.assertEqual(
  210. set(c.name for c in project.containers()),
  211. set([web_container_1.name, web_container_2.name, db_container.name]))
  212. project.pause(service_names=['web'])
  213. self.assertEqual(
  214. set([c.name for c in project.containers() if c.is_paused]),
  215. set([web_container_1.name, web_container_2.name]))
  216. project.pause()
  217. self.assertEqual(
  218. set([c.name for c in project.containers() if c.is_paused]),
  219. set([web_container_1.name, web_container_2.name, db_container.name]))
  220. project.unpause(service_names=['db'])
  221. self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 2)
  222. project.unpause()
  223. self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 0)
  224. project.stop(service_names=['web'], timeout=1)
  225. self.assertEqual(set(c.name for c in project.containers()), set([db_container.name]))
  226. project.kill(service_names=['db'])
  227. self.assertEqual(len(project.containers()), 0)
  228. self.assertEqual(len(project.containers(stopped=True)), 3)
  229. project.remove_stopped(service_names=['web'])
  230. self.assertEqual(len(project.containers(stopped=True)), 1)
  231. project.remove_stopped()
  232. self.assertEqual(len(project.containers(stopped=True)), 0)
  233. def test_create(self):
  234. web = self.create_service('web')
  235. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  236. project = Project('composetest', [web, db], self.client)
  237. project.create(['db'])
  238. self.assertEqual(len(project.containers()), 0)
  239. self.assertEqual(len(project.containers(stopped=True)), 1)
  240. self.assertEqual(len(db.containers()), 0)
  241. self.assertEqual(len(db.containers(stopped=True)), 1)
  242. self.assertEqual(len(web.containers(stopped=True)), 0)
  243. def test_create_twice(self):
  244. web = self.create_service('web')
  245. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  246. project = Project('composetest', [web, db], self.client)
  247. project.create(['db', 'web'])
  248. project.create(['db', 'web'])
  249. self.assertEqual(len(project.containers()), 0)
  250. self.assertEqual(len(project.containers(stopped=True)), 2)
  251. self.assertEqual(len(db.containers()), 0)
  252. self.assertEqual(len(db.containers(stopped=True)), 1)
  253. self.assertEqual(len(web.containers()), 0)
  254. self.assertEqual(len(web.containers(stopped=True)), 1)
  255. def test_create_with_links(self):
  256. db = self.create_service('db')
  257. web = self.create_service('web', links=[(db, 'db')])
  258. project = Project('composetest', [db, web], self.client)
  259. project.create(['web'])
  260. self.assertEqual(len(project.containers()), 0)
  261. self.assertEqual(len(project.containers(stopped=True)), 2)
  262. self.assertEqual(len(db.containers()), 0)
  263. self.assertEqual(len(db.containers(stopped=True)), 1)
  264. self.assertEqual(len(web.containers()), 0)
  265. self.assertEqual(len(web.containers(stopped=True)), 1)
  266. def test_create_strategy_always(self):
  267. db = self.create_service('db')
  268. project = Project('composetest', [db], self.client)
  269. project.create(['db'])
  270. old_id = project.containers(stopped=True)[0].id
  271. project.create(['db'], strategy=ConvergenceStrategy.always)
  272. self.assertEqual(len(project.containers()), 0)
  273. self.assertEqual(len(project.containers(stopped=True)), 1)
  274. db_container = project.containers(stopped=True)[0]
  275. self.assertNotEqual(db_container.id, old_id)
  276. def test_create_strategy_never(self):
  277. db = self.create_service('db')
  278. project = Project('composetest', [db], self.client)
  279. project.create(['db'])
  280. old_id = project.containers(stopped=True)[0].id
  281. project.create(['db'], strategy=ConvergenceStrategy.never)
  282. self.assertEqual(len(project.containers()), 0)
  283. self.assertEqual(len(project.containers(stopped=True)), 1)
  284. db_container = project.containers(stopped=True)[0]
  285. self.assertEqual(db_container.id, old_id)
  286. def test_project_up(self):
  287. web = self.create_service('web')
  288. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  289. project = Project('composetest', [web, db], self.client)
  290. project.start()
  291. self.assertEqual(len(project.containers()), 0)
  292. project.up(['db'])
  293. self.assertEqual(len(project.containers()), 1)
  294. self.assertEqual(len(db.containers()), 1)
  295. self.assertEqual(len(web.containers()), 0)
  296. def test_project_up_starts_uncreated_services(self):
  297. db = self.create_service('db')
  298. web = self.create_service('web', links=[(db, 'db')])
  299. project = Project('composetest', [db, web], self.client)
  300. project.up(['db'])
  301. self.assertEqual(len(project.containers()), 1)
  302. project.up()
  303. self.assertEqual(len(project.containers()), 2)
  304. self.assertEqual(len(db.containers()), 1)
  305. self.assertEqual(len(web.containers()), 1)
  306. def test_recreate_preserves_volumes(self):
  307. web = self.create_service('web')
  308. db = self.create_service('db', volumes=[VolumeSpec.parse('/etc')])
  309. project = Project('composetest', [web, db], self.client)
  310. project.start()
  311. self.assertEqual(len(project.containers()), 0)
  312. project.up(['db'])
  313. self.assertEqual(len(project.containers()), 1)
  314. old_db_id = project.containers()[0].id
  315. db_volume_path = project.containers()[0].get('Volumes./etc')
  316. project.up(strategy=ConvergenceStrategy.always)
  317. self.assertEqual(len(project.containers()), 2)
  318. db_container = [c for c in project.containers() if 'db' in c.name][0]
  319. self.assertNotEqual(db_container.id, old_db_id)
  320. self.assertEqual(db_container.get('Volumes./etc'), db_volume_path)
  321. def test_project_up_with_no_recreate_running(self):
  322. web = self.create_service('web')
  323. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  324. project = Project('composetest', [web, db], self.client)
  325. project.start()
  326. self.assertEqual(len(project.containers()), 0)
  327. project.up(['db'])
  328. self.assertEqual(len(project.containers()), 1)
  329. old_db_id = project.containers()[0].id
  330. container, = project.containers()
  331. db_volume_path = container.get_mount('/var/db')['Source']
  332. project.up(strategy=ConvergenceStrategy.never)
  333. self.assertEqual(len(project.containers()), 2)
  334. db_container = [c for c in project.containers() if 'db' in c.name][0]
  335. self.assertEqual(db_container.id, old_db_id)
  336. self.assertEqual(
  337. db_container.get_mount('/var/db')['Source'],
  338. db_volume_path)
  339. def test_project_up_with_no_recreate_stopped(self):
  340. web = self.create_service('web')
  341. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  342. project = Project('composetest', [web, db], self.client)
  343. project.start()
  344. self.assertEqual(len(project.containers()), 0)
  345. project.up(['db'])
  346. project.kill()
  347. old_containers = project.containers(stopped=True)
  348. self.assertEqual(len(old_containers), 1)
  349. old_container, = old_containers
  350. old_db_id = old_container.id
  351. db_volume_path = old_container.get_mount('/var/db')['Source']
  352. project.up(strategy=ConvergenceStrategy.never)
  353. new_containers = project.containers(stopped=True)
  354. self.assertEqual(len(new_containers), 2)
  355. self.assertEqual([c.is_running for c in new_containers], [True, True])
  356. db_container = [c for c in new_containers if 'db' in c.name][0]
  357. self.assertEqual(db_container.id, old_db_id)
  358. self.assertEqual(
  359. db_container.get_mount('/var/db')['Source'],
  360. db_volume_path)
  361. def test_project_up_without_all_services(self):
  362. console = self.create_service('console')
  363. db = self.create_service('db')
  364. project = Project('composetest', [console, db], self.client)
  365. project.start()
  366. self.assertEqual(len(project.containers()), 0)
  367. project.up()
  368. self.assertEqual(len(project.containers()), 2)
  369. self.assertEqual(len(db.containers()), 1)
  370. self.assertEqual(len(console.containers()), 1)
  371. def test_project_up_starts_links(self):
  372. console = self.create_service('console')
  373. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  374. web = self.create_service('web', links=[(db, 'db')])
  375. project = Project('composetest', [web, db, console], self.client)
  376. project.start()
  377. self.assertEqual(len(project.containers()), 0)
  378. project.up(['web'])
  379. self.assertEqual(len(project.containers()), 2)
  380. self.assertEqual(len(web.containers()), 1)
  381. self.assertEqual(len(db.containers()), 1)
  382. self.assertEqual(len(console.containers()), 0)
  383. def test_project_up_starts_depends(self):
  384. project = Project.from_config(
  385. name='composetest',
  386. config_data=build_config({
  387. 'console': {
  388. 'image': 'busybox:latest',
  389. 'command': ["top"],
  390. },
  391. 'data': {
  392. 'image': 'busybox:latest',
  393. 'command': ["top"]
  394. },
  395. 'db': {
  396. 'image': 'busybox:latest',
  397. 'command': ["top"],
  398. 'volumes_from': ['data'],
  399. },
  400. 'web': {
  401. 'image': 'busybox:latest',
  402. 'command': ["top"],
  403. 'links': ['db'],
  404. },
  405. }),
  406. client=self.client,
  407. )
  408. project.start()
  409. self.assertEqual(len(project.containers()), 0)
  410. project.up(['web'])
  411. self.assertEqual(len(project.containers()), 3)
  412. self.assertEqual(len(project.get_service('web').containers()), 1)
  413. self.assertEqual(len(project.get_service('db').containers()), 1)
  414. self.assertEqual(len(project.get_service('data').containers()), 1)
  415. self.assertEqual(len(project.get_service('console').containers()), 0)
  416. def test_project_up_with_no_deps(self):
  417. project = Project.from_config(
  418. name='composetest',
  419. config_data=build_config({
  420. 'console': {
  421. 'image': 'busybox:latest',
  422. 'command': ["top"],
  423. },
  424. 'data': {
  425. 'image': 'busybox:latest',
  426. 'command': ["top"]
  427. },
  428. 'db': {
  429. 'image': 'busybox:latest',
  430. 'command': ["top"],
  431. 'volumes_from': ['data'],
  432. },
  433. 'web': {
  434. 'image': 'busybox:latest',
  435. 'command': ["top"],
  436. 'links': ['db'],
  437. },
  438. }),
  439. client=self.client,
  440. )
  441. project.start()
  442. self.assertEqual(len(project.containers()), 0)
  443. project.up(['db'], start_deps=False)
  444. self.assertEqual(len(project.containers(stopped=True)), 2)
  445. self.assertEqual(len(project.get_service('web').containers()), 0)
  446. self.assertEqual(len(project.get_service('db').containers()), 1)
  447. self.assertEqual(len(project.get_service('data').containers()), 0)
  448. self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
  449. self.assertEqual(len(project.get_service('console').containers()), 0)
  450. def test_unscale_after_restart(self):
  451. web = self.create_service('web')
  452. project = Project('composetest', [web], self.client)
  453. project.start()
  454. service = project.get_service('web')
  455. service.scale(1)
  456. self.assertEqual(len(service.containers()), 1)
  457. service.scale(3)
  458. self.assertEqual(len(service.containers()), 3)
  459. project.up()
  460. service = project.get_service('web')
  461. self.assertEqual(len(service.containers()), 3)
  462. service.scale(1)
  463. self.assertEqual(len(service.containers()), 1)
  464. project.up()
  465. service = project.get_service('web')
  466. self.assertEqual(len(service.containers()), 1)
  467. # does scale=0 ,makes any sense? after recreating at least 1 container is running
  468. service.scale(0)
  469. project.up()
  470. service = project.get_service('web')
  471. self.assertEqual(len(service.containers()), 1)
  472. @v2_only()
  473. def test_project_up_networks(self):
  474. config_data = config.Config(
  475. version=V2_0,
  476. services=[{
  477. 'name': 'web',
  478. 'image': 'busybox:latest',
  479. 'command': 'top',
  480. 'networks': {
  481. 'foo': None,
  482. 'bar': None,
  483. 'baz': {'aliases': ['extra']},
  484. },
  485. }],
  486. volumes={},
  487. networks={
  488. 'foo': {'driver': 'bridge'},
  489. 'bar': {'driver': None},
  490. 'baz': {},
  491. },
  492. )
  493. project = Project.from_config(
  494. client=self.client,
  495. name='composetest',
  496. config_data=config_data,
  497. )
  498. project.up()
  499. containers = project.containers()
  500. assert len(containers) == 1
  501. container, = containers
  502. for net_name in ['foo', 'bar', 'baz']:
  503. full_net_name = 'composetest_{}'.format(net_name)
  504. network_data = self.client.inspect_network(full_net_name)
  505. assert network_data['Name'] == full_net_name
  506. aliases_key = 'NetworkSettings.Networks.{net}.Aliases'
  507. assert 'web' in container.get(aliases_key.format(net='composetest_foo'))
  508. assert 'web' in container.get(aliases_key.format(net='composetest_baz'))
  509. assert 'extra' in container.get(aliases_key.format(net='composetest_baz'))
  510. foo_data = self.client.inspect_network('composetest_foo')
  511. assert foo_data['Driver'] == 'bridge'
  512. @v2_only()
  513. def test_up_with_ipam_config(self):
  514. config_data = config.Config(
  515. version=V2_0,
  516. services=[{
  517. 'name': 'web',
  518. 'image': 'busybox:latest',
  519. 'networks': {'front': None},
  520. }],
  521. volumes={},
  522. networks={
  523. 'front': {
  524. 'driver': 'bridge',
  525. 'driver_opts': {
  526. "com.docker.network.bridge.enable_icc": "false",
  527. },
  528. 'ipam': {
  529. 'driver': 'default',
  530. 'config': [{
  531. "subnet": "172.28.0.0/16",
  532. "ip_range": "172.28.5.0/24",
  533. "gateway": "172.28.5.254",
  534. "aux_addresses": {
  535. "a": "172.28.1.5",
  536. "b": "172.28.1.6",
  537. "c": "172.28.1.7",
  538. },
  539. }],
  540. },
  541. },
  542. },
  543. )
  544. project = Project.from_config(
  545. client=self.client,
  546. name='composetest',
  547. config_data=config_data,
  548. )
  549. project.up()
  550. network = self.client.networks(names=['composetest_front'])[0]
  551. assert network['Options'] == {
  552. "com.docker.network.bridge.enable_icc": "false"
  553. }
  554. assert network['IPAM'] == {
  555. 'Driver': 'default',
  556. 'Options': None,
  557. 'Config': [{
  558. 'Subnet': "172.28.0.0/16",
  559. 'IPRange': "172.28.5.0/24",
  560. 'Gateway': "172.28.5.254",
  561. 'AuxiliaryAddresses': {
  562. 'a': '172.28.1.5',
  563. 'b': '172.28.1.6',
  564. 'c': '172.28.1.7',
  565. },
  566. }],
  567. }
  568. @v2_only()
  569. def test_up_with_network_static_addresses(self):
  570. config_data = config.Config(
  571. version=V2_0,
  572. services=[{
  573. 'name': 'web',
  574. 'image': 'busybox:latest',
  575. 'command': 'top',
  576. 'networks': {
  577. 'static_test': {
  578. 'ipv4_address': '172.16.100.100',
  579. 'ipv6_address': 'fe80::1001:102'
  580. }
  581. },
  582. }],
  583. volumes={},
  584. networks={
  585. 'static_test': {
  586. 'driver': 'bridge',
  587. 'driver_opts': {
  588. "com.docker.network.enable_ipv6": "true",
  589. },
  590. 'ipam': {
  591. 'driver': 'default',
  592. 'config': [
  593. {"subnet": "172.16.100.0/24",
  594. "gateway": "172.16.100.1"},
  595. {"subnet": "fe80::/64",
  596. "gateway": "fe80::1001:1"}
  597. ]
  598. }
  599. }
  600. }
  601. )
  602. project = Project.from_config(
  603. client=self.client,
  604. name='composetest',
  605. config_data=config_data,
  606. )
  607. project.up(detached=True)
  608. network = self.client.networks(names=['static_test'])[0]
  609. service_container = project.get_service('web').containers()[0]
  610. assert network['Options'] == {
  611. "com.docker.network.enable_ipv6": "true"
  612. }
  613. IPAMConfig = (service_container.inspect().get('NetworkSettings', {}).
  614. get('Networks', {}).get('composetest_static_test', {}).
  615. get('IPAMConfig', {}))
  616. assert IPAMConfig.get('IPv4Address') == '172.16.100.100'
  617. assert IPAMConfig.get('IPv6Address') == 'fe80::1001:102'
  618. @v2_1_only()
  619. def test_up_with_enable_ipv6(self):
  620. self.require_api_version('1.23')
  621. config_data = config.Config(
  622. version=V2_0,
  623. services=[{
  624. 'name': 'web',
  625. 'image': 'busybox:latest',
  626. 'command': 'top',
  627. 'networks': {
  628. 'static_test': {
  629. 'ipv6_address': 'fe80::1001:102'
  630. }
  631. },
  632. }],
  633. volumes={},
  634. networks={
  635. 'static_test': {
  636. 'driver': 'bridge',
  637. 'enable_ipv6': True,
  638. 'ipam': {
  639. 'driver': 'default',
  640. 'config': [
  641. {"subnet": "fe80::/64",
  642. "gateway": "fe80::1001:1"}
  643. ]
  644. }
  645. }
  646. }
  647. )
  648. project = Project.from_config(
  649. client=self.client,
  650. name='composetest',
  651. config_data=config_data,
  652. )
  653. project.up(detached=True)
  654. network = self.client.networks(names=['static_test'])[0]
  655. service_container = project.get_service('web').containers()[0]
  656. assert network['EnableIPv6'] is True
  657. ipam_config = (service_container.inspect().get('NetworkSettings', {}).
  658. get('Networks', {}).get('composetest_static_test', {}).
  659. get('IPAMConfig', {}))
  660. assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
  661. @v2_only()
  662. def test_up_with_network_static_addresses_missing_subnet(self):
  663. config_data = config.Config(
  664. version=V2_0,
  665. services=[{
  666. 'name': 'web',
  667. 'image': 'busybox:latest',
  668. 'networks': {
  669. 'static_test': {
  670. 'ipv4_address': '172.16.100.100',
  671. 'ipv6_address': 'fe80::1001:101'
  672. }
  673. },
  674. }],
  675. volumes={},
  676. networks={
  677. 'static_test': {
  678. 'driver': 'bridge',
  679. 'driver_opts': {
  680. "com.docker.network.enable_ipv6": "true",
  681. },
  682. 'ipam': {
  683. 'driver': 'default',
  684. },
  685. },
  686. },
  687. )
  688. project = Project.from_config(
  689. client=self.client,
  690. name='composetest',
  691. config_data=config_data,
  692. )
  693. with self.assertRaises(ProjectError):
  694. project.up()
  695. @v2_1_only()
  696. def test_up_with_network_link_local_ips(self):
  697. config_data = config.Config(
  698. version=V2_1,
  699. services=[{
  700. 'name': 'web',
  701. 'image': 'busybox:latest',
  702. 'networks': {
  703. 'linklocaltest': {
  704. 'link_local_ips': ['169.254.8.8']
  705. }
  706. }
  707. }],
  708. volumes={},
  709. networks={
  710. 'linklocaltest': {'driver': 'bridge'}
  711. }
  712. )
  713. project = Project.from_config(
  714. client=self.client,
  715. name='composetest',
  716. config_data=config_data
  717. )
  718. project.up(detached=True)
  719. service_container = project.get_service('web').containers(stopped=True)[0]
  720. ipam_config = service_container.inspect().get(
  721. 'NetworkSettings', {}
  722. ).get(
  723. 'Networks', {}
  724. ).get(
  725. 'composetest_linklocaltest', {}
  726. ).get('IPAMConfig', {})
  727. assert 'LinkLocalIPs' in ipam_config
  728. assert ipam_config['LinkLocalIPs'] == ['169.254.8.8']
  729. @v2_1_only()
  730. def test_up_with_isolation(self):
  731. self.require_api_version('1.24')
  732. config_data = config.Config(
  733. version=V2_1,
  734. services=[{
  735. 'name': 'web',
  736. 'image': 'busybox:latest',
  737. 'isolation': 'default'
  738. }],
  739. volumes={},
  740. networks={}
  741. )
  742. project = Project.from_config(
  743. client=self.client,
  744. name='composetest',
  745. config_data=config_data
  746. )
  747. project.up(detached=True)
  748. service_container = project.get_service('web').containers(stopped=True)[0]
  749. assert service_container.inspect()['HostConfig']['Isolation'] == 'default'
  750. @v2_1_only()
  751. def test_up_with_invalid_isolation(self):
  752. self.require_api_version('1.24')
  753. config_data = config.Config(
  754. version=V2_1,
  755. services=[{
  756. 'name': 'web',
  757. 'image': 'busybox:latest',
  758. 'isolation': 'foobar'
  759. }],
  760. volumes={},
  761. networks={}
  762. )
  763. project = Project.from_config(
  764. client=self.client,
  765. name='composetest',
  766. config_data=config_data
  767. )
  768. with self.assertRaises(ProjectError):
  769. project.up()
  770. @v2_only()
  771. def test_project_up_with_network_internal(self):
  772. self.require_api_version('1.23')
  773. config_data = config.Config(
  774. version=V2_0,
  775. services=[{
  776. 'name': 'web',
  777. 'image': 'busybox:latest',
  778. 'networks': {'internal': None},
  779. }],
  780. volumes={},
  781. networks={
  782. 'internal': {'driver': 'bridge', 'internal': True},
  783. },
  784. )
  785. project = Project.from_config(
  786. client=self.client,
  787. name='composetest',
  788. config_data=config_data,
  789. )
  790. project.up()
  791. network = self.client.networks(names=['composetest_internal'])[0]
  792. assert network['Internal'] is True
  793. @v2_1_only()
  794. def test_project_up_with_network_label(self):
  795. self.require_api_version('1.23')
  796. network_name = 'network_with_label'
  797. config_data = config.Config(
  798. version=V2_0,
  799. services=[{
  800. 'name': 'web',
  801. 'image': 'busybox:latest',
  802. 'networks': {network_name: None}
  803. }],
  804. volumes={},
  805. networks={
  806. network_name: {'labels': {'label_key': 'label_val'}}
  807. }
  808. )
  809. project = Project.from_config(
  810. client=self.client,
  811. name='composetest',
  812. config_data=config_data
  813. )
  814. project.up()
  815. networks = [
  816. n for n in self.client.networks()
  817. if n['Name'].startswith('composetest_')
  818. ]
  819. assert [n['Name'] for n in networks] == ['composetest_{}'.format(network_name)]
  820. assert 'label_key' in networks[0]['Labels']
  821. assert networks[0]['Labels']['label_key'] == 'label_val'
  822. @v2_only()
  823. def test_project_up_volumes(self):
  824. vol_name = '{0:x}'.format(random.getrandbits(32))
  825. full_vol_name = 'composetest_{0}'.format(vol_name)
  826. config_data = config.Config(
  827. version=V2_0,
  828. services=[{
  829. 'name': 'web',
  830. 'image': 'busybox:latest',
  831. 'command': 'top'
  832. }],
  833. volumes={vol_name: {'driver': 'local'}},
  834. networks={},
  835. )
  836. project = Project.from_config(
  837. name='composetest',
  838. config_data=config_data, client=self.client
  839. )
  840. project.up()
  841. self.assertEqual(len(project.containers()), 1)
  842. volume_data = self.client.inspect_volume(full_vol_name)
  843. self.assertEqual(volume_data['Name'], full_vol_name)
  844. self.assertEqual(volume_data['Driver'], 'local')
  845. @v2_1_only()
  846. def test_project_up_with_volume_labels(self):
  847. self.require_api_version('1.23')
  848. volume_name = 'volume_with_label'
  849. config_data = config.Config(
  850. version=V2_0,
  851. services=[{
  852. 'name': 'web',
  853. 'image': 'busybox:latest',
  854. 'volumes': [VolumeSpec.parse('{}:/data'.format(volume_name))]
  855. }],
  856. volumes={
  857. volume_name: {
  858. 'labels': {
  859. 'label_key': 'label_val'
  860. }
  861. }
  862. },
  863. networks={},
  864. )
  865. project = Project.from_config(
  866. client=self.client,
  867. name='composetest',
  868. config_data=config_data,
  869. )
  870. project.up()
  871. volumes = [
  872. v for v in self.client.volumes().get('Volumes', [])
  873. if v['Name'].startswith('composetest_')
  874. ]
  875. assert [v['Name'] for v in volumes] == ['composetest_{}'.format(volume_name)]
  876. assert 'label_key' in volumes[0]['Labels']
  877. assert volumes[0]['Labels']['label_key'] == 'label_val'
  878. @v2_only()
  879. def test_project_up_logging_with_multiple_files(self):
  880. base_file = config.ConfigFile(
  881. 'base.yml',
  882. {
  883. 'version': V2_0,
  884. 'services': {
  885. 'simple': {'image': 'busybox:latest', 'command': 'top'},
  886. 'another': {
  887. 'image': 'busybox:latest',
  888. 'command': 'top',
  889. 'logging': {
  890. 'driver': "json-file",
  891. 'options': {
  892. 'max-size': "10m"
  893. }
  894. }
  895. }
  896. }
  897. })
  898. override_file = config.ConfigFile(
  899. 'override.yml',
  900. {
  901. 'version': V2_0,
  902. 'services': {
  903. 'another': {
  904. 'logging': {
  905. 'driver': "none"
  906. }
  907. }
  908. }
  909. })
  910. details = config.ConfigDetails('.', [base_file, override_file])
  911. tmpdir = py.test.ensuretemp('logging_test')
  912. self.addCleanup(tmpdir.remove)
  913. with tmpdir.as_cwd():
  914. config_data = config.load(details)
  915. project = Project.from_config(
  916. name='composetest', config_data=config_data, client=self.client
  917. )
  918. project.up()
  919. containers = project.containers()
  920. self.assertEqual(len(containers), 2)
  921. another = project.get_service('another').containers()[0]
  922. log_config = another.get('HostConfig.LogConfig')
  923. self.assertTrue(log_config)
  924. self.assertEqual(log_config.get('Type'), 'none')
  925. @v2_only()
  926. def test_project_up_port_mappings_with_multiple_files(self):
  927. base_file = config.ConfigFile(
  928. 'base.yml',
  929. {
  930. 'version': V2_0,
  931. 'services': {
  932. 'simple': {
  933. 'image': 'busybox:latest',
  934. 'command': 'top',
  935. 'ports': ['1234:1234']
  936. },
  937. },
  938. })
  939. override_file = config.ConfigFile(
  940. 'override.yml',
  941. {
  942. 'version': V2_0,
  943. 'services': {
  944. 'simple': {
  945. 'ports': ['1234:1234']
  946. }
  947. }
  948. })
  949. details = config.ConfigDetails('.', [base_file, override_file])
  950. config_data = config.load(details)
  951. project = Project.from_config(
  952. name='composetest', config_data=config_data, client=self.client
  953. )
  954. project.up()
  955. containers = project.containers()
  956. self.assertEqual(len(containers), 1)
  957. @v2_only()
  958. def test_initialize_volumes(self):
  959. vol_name = '{0:x}'.format(random.getrandbits(32))
  960. full_vol_name = 'composetest_{0}'.format(vol_name)
  961. config_data = config.Config(
  962. version=V2_0,
  963. services=[{
  964. 'name': 'web',
  965. 'image': 'busybox:latest',
  966. 'command': 'top'
  967. }],
  968. volumes={vol_name: {}},
  969. networks={},
  970. )
  971. project = Project.from_config(
  972. name='composetest',
  973. config_data=config_data, client=self.client
  974. )
  975. project.volumes.initialize()
  976. volume_data = self.client.inspect_volume(full_vol_name)
  977. self.assertEqual(volume_data['Name'], full_vol_name)
  978. self.assertEqual(volume_data['Driver'], 'local')
  979. @v2_only()
  980. def test_project_up_implicit_volume_driver(self):
  981. vol_name = '{0:x}'.format(random.getrandbits(32))
  982. full_vol_name = 'composetest_{0}'.format(vol_name)
  983. config_data = config.Config(
  984. version=V2_0,
  985. services=[{
  986. 'name': 'web',
  987. 'image': 'busybox:latest',
  988. 'command': 'top'
  989. }],
  990. volumes={vol_name: {}},
  991. networks={},
  992. )
  993. project = Project.from_config(
  994. name='composetest',
  995. config_data=config_data, client=self.client
  996. )
  997. project.up()
  998. volume_data = self.client.inspect_volume(full_vol_name)
  999. self.assertEqual(volume_data['Name'], full_vol_name)
  1000. self.assertEqual(volume_data['Driver'], 'local')
  1001. @v2_only()
  1002. def test_initialize_volumes_invalid_volume_driver(self):
  1003. vol_name = '{0:x}'.format(random.getrandbits(32))
  1004. config_data = config.Config(
  1005. version=V2_0,
  1006. services=[{
  1007. 'name': 'web',
  1008. 'image': 'busybox:latest',
  1009. 'command': 'top'
  1010. }],
  1011. volumes={vol_name: {'driver': 'foobar'}},
  1012. networks={},
  1013. )
  1014. project = Project.from_config(
  1015. name='composetest',
  1016. config_data=config_data, client=self.client
  1017. )
  1018. with self.assertRaises(config.ConfigurationError):
  1019. project.volumes.initialize()
  1020. @v2_only()
  1021. def test_initialize_volumes_updated_driver(self):
  1022. vol_name = '{0:x}'.format(random.getrandbits(32))
  1023. full_vol_name = 'composetest_{0}'.format(vol_name)
  1024. config_data = config.Config(
  1025. version=V2_0,
  1026. services=[{
  1027. 'name': 'web',
  1028. 'image': 'busybox:latest',
  1029. 'command': 'top'
  1030. }],
  1031. volumes={vol_name: {'driver': 'local'}},
  1032. networks={},
  1033. )
  1034. project = Project.from_config(
  1035. name='composetest',
  1036. config_data=config_data, client=self.client
  1037. )
  1038. project.volumes.initialize()
  1039. volume_data = self.client.inspect_volume(full_vol_name)
  1040. self.assertEqual(volume_data['Name'], full_vol_name)
  1041. self.assertEqual(volume_data['Driver'], 'local')
  1042. config_data = config_data._replace(
  1043. volumes={vol_name: {'driver': 'smb'}}
  1044. )
  1045. project = Project.from_config(
  1046. name='composetest',
  1047. config_data=config_data,
  1048. client=self.client
  1049. )
  1050. with self.assertRaises(config.ConfigurationError) as e:
  1051. project.volumes.initialize()
  1052. assert 'Configuration for volume {0} specifies driver smb'.format(
  1053. vol_name
  1054. ) in str(e.exception)
  1055. @v2_only()
  1056. def test_initialize_volumes_updated_blank_driver(self):
  1057. vol_name = '{0:x}'.format(random.getrandbits(32))
  1058. full_vol_name = 'composetest_{0}'.format(vol_name)
  1059. config_data = config.Config(
  1060. version=V2_0,
  1061. services=[{
  1062. 'name': 'web',
  1063. 'image': 'busybox:latest',
  1064. 'command': 'top'
  1065. }],
  1066. volumes={vol_name: {'driver': 'local'}},
  1067. networks={},
  1068. )
  1069. project = Project.from_config(
  1070. name='composetest',
  1071. config_data=config_data, client=self.client
  1072. )
  1073. project.volumes.initialize()
  1074. volume_data = self.client.inspect_volume(full_vol_name)
  1075. self.assertEqual(volume_data['Name'], full_vol_name)
  1076. self.assertEqual(volume_data['Driver'], 'local')
  1077. config_data = config_data._replace(
  1078. volumes={vol_name: {}}
  1079. )
  1080. project = Project.from_config(
  1081. name='composetest',
  1082. config_data=config_data,
  1083. client=self.client
  1084. )
  1085. project.volumes.initialize()
  1086. volume_data = self.client.inspect_volume(full_vol_name)
  1087. self.assertEqual(volume_data['Name'], full_vol_name)
  1088. self.assertEqual(volume_data['Driver'], 'local')
  1089. @v2_only()
  1090. def test_initialize_volumes_external_volumes(self):
  1091. # Use composetest_ prefix so it gets garbage-collected in tearDown()
  1092. vol_name = 'composetest_{0:x}'.format(random.getrandbits(32))
  1093. full_vol_name = 'composetest_{0}'.format(vol_name)
  1094. self.client.create_volume(vol_name)
  1095. config_data = config.Config(
  1096. version=V2_0,
  1097. services=[{
  1098. 'name': 'web',
  1099. 'image': 'busybox:latest',
  1100. 'command': 'top'
  1101. }],
  1102. volumes={
  1103. vol_name: {'external': True, 'external_name': vol_name}
  1104. },
  1105. networks=None,
  1106. )
  1107. project = Project.from_config(
  1108. name='composetest',
  1109. config_data=config_data, client=self.client
  1110. )
  1111. project.volumes.initialize()
  1112. with self.assertRaises(NotFound):
  1113. self.client.inspect_volume(full_vol_name)
  1114. @v2_only()
  1115. def test_initialize_volumes_inexistent_external_volume(self):
  1116. vol_name = '{0:x}'.format(random.getrandbits(32))
  1117. config_data = config.Config(
  1118. version=V2_0,
  1119. services=[{
  1120. 'name': 'web',
  1121. 'image': 'busybox:latest',
  1122. 'command': 'top'
  1123. }],
  1124. volumes={
  1125. vol_name: {'external': True, 'external_name': vol_name}
  1126. },
  1127. networks=None,
  1128. )
  1129. project = Project.from_config(
  1130. name='composetest',
  1131. config_data=config_data, client=self.client
  1132. )
  1133. with self.assertRaises(config.ConfigurationError) as e:
  1134. project.volumes.initialize()
  1135. assert 'Volume {0} declared as external'.format(
  1136. vol_name
  1137. ) in str(e.exception)
  1138. @v2_only()
  1139. def test_project_up_named_volumes_in_binds(self):
  1140. vol_name = '{0:x}'.format(random.getrandbits(32))
  1141. full_vol_name = 'composetest_{0}'.format(vol_name)
  1142. base_file = config.ConfigFile(
  1143. 'base.yml',
  1144. {
  1145. 'version': V2_0,
  1146. 'services': {
  1147. 'simple': {
  1148. 'image': 'busybox:latest',
  1149. 'command': 'top',
  1150. 'volumes': ['{0}:/data'.format(vol_name)]
  1151. },
  1152. },
  1153. 'volumes': {
  1154. vol_name: {'driver': 'local'}
  1155. }
  1156. })
  1157. config_details = config.ConfigDetails('.', [base_file])
  1158. config_data = config.load(config_details)
  1159. project = Project.from_config(
  1160. name='composetest', config_data=config_data, client=self.client
  1161. )
  1162. service = project.services[0]
  1163. self.assertEqual(service.name, 'simple')
  1164. volumes = service.options.get('volumes')
  1165. self.assertEqual(len(volumes), 1)
  1166. self.assertEqual(volumes[0].external, full_vol_name)
  1167. project.up()
  1168. engine_volumes = self.client.volumes()['Volumes']
  1169. container = service.get_container()
  1170. assert [mount['Name'] for mount in container.get('Mounts')] == [full_vol_name]
  1171. assert next((v for v in engine_volumes if v['Name'] == vol_name), None) is None
  1172. def test_project_up_orphans(self):
  1173. config_dict = {
  1174. 'service1': {
  1175. 'image': 'busybox:latest',
  1176. 'command': 'top',
  1177. }
  1178. }
  1179. config_data = build_config(config_dict)
  1180. project = Project.from_config(
  1181. name='composetest', config_data=config_data, client=self.client
  1182. )
  1183. project.up()
  1184. config_dict['service2'] = config_dict['service1']
  1185. del config_dict['service1']
  1186. config_data = build_config(config_dict)
  1187. project = Project.from_config(
  1188. name='composetest', config_data=config_data, client=self.client
  1189. )
  1190. with mock.patch('compose.project.log') as mock_log:
  1191. project.up()
  1192. mock_log.warning.assert_called_once_with(mock.ANY)
  1193. assert len([
  1194. ctnr for ctnr in project._labeled_containers()
  1195. if ctnr.labels.get(LABEL_SERVICE) == 'service1'
  1196. ]) == 1
  1197. project.up(remove_orphans=True)
  1198. assert len([
  1199. ctnr for ctnr in project._labeled_containers()
  1200. if ctnr.labels.get(LABEL_SERVICE) == 'service1'
  1201. ]) == 0
  1202. @v2_1_only()
  1203. def test_project_up_healthy_dependency(self):
  1204. config_dict = {
  1205. 'version': '2.1',
  1206. 'services': {
  1207. 'svc1': {
  1208. 'image': 'busybox:latest',
  1209. 'command': 'top',
  1210. 'healthcheck': {
  1211. 'test': 'exit 0',
  1212. 'retries': 1,
  1213. 'timeout': '10s',
  1214. 'interval': '0.1s'
  1215. },
  1216. },
  1217. 'svc2': {
  1218. 'image': 'busybox:latest',
  1219. 'command': 'top',
  1220. 'depends_on': {
  1221. 'svc1': {'condition': 'service_healthy'},
  1222. }
  1223. }
  1224. }
  1225. }
  1226. config_data = build_config(config_dict)
  1227. project = Project.from_config(
  1228. name='composetest', config_data=config_data, client=self.client
  1229. )
  1230. project.up()
  1231. containers = project.containers()
  1232. assert len(containers) == 2
  1233. svc1 = project.get_service('svc1')
  1234. svc2 = project.get_service('svc2')
  1235. assert 'svc1' in svc2.get_dependency_names()
  1236. assert svc1.is_healthy()
  1237. @v2_1_only()
  1238. def test_project_up_unhealthy_dependency(self):
  1239. config_dict = {
  1240. 'version': '2.1',
  1241. 'services': {
  1242. 'svc1': {
  1243. 'image': 'busybox:latest',
  1244. 'command': 'top',
  1245. 'healthcheck': {
  1246. 'test': 'exit 1',
  1247. 'retries': 1,
  1248. 'timeout': '10s',
  1249. 'interval': '0.1s'
  1250. },
  1251. },
  1252. 'svc2': {
  1253. 'image': 'busybox:latest',
  1254. 'command': 'top',
  1255. 'depends_on': {
  1256. 'svc1': {'condition': 'service_healthy'},
  1257. }
  1258. }
  1259. }
  1260. }
  1261. config_data = build_config(config_dict)
  1262. project = Project.from_config(
  1263. name='composetest', config_data=config_data, client=self.client
  1264. )
  1265. with pytest.raises(HealthCheckFailed):
  1266. project.up()
  1267. containers = project.containers()
  1268. assert len(containers) == 1
  1269. svc1 = project.get_service('svc1')
  1270. svc2 = project.get_service('svc2')
  1271. assert 'svc1' in svc2.get_dependency_names()
  1272. with pytest.raises(HealthCheckFailed):
  1273. svc1.is_healthy()
  1274. @v2_1_only()
  1275. def test_project_up_no_healthcheck_dependency(self):
  1276. config_dict = {
  1277. 'version': '2.1',
  1278. 'services': {
  1279. 'svc1': {
  1280. 'image': 'busybox:latest',
  1281. 'command': 'top',
  1282. 'healthcheck': {
  1283. 'disable': True
  1284. },
  1285. },
  1286. 'svc2': {
  1287. 'image': 'busybox:latest',
  1288. 'command': 'top',
  1289. 'depends_on': {
  1290. 'svc1': {'condition': 'service_healthy'},
  1291. }
  1292. }
  1293. }
  1294. }
  1295. config_data = build_config(config_dict)
  1296. project = Project.from_config(
  1297. name='composetest', config_data=config_data, client=self.client
  1298. )
  1299. with pytest.raises(NoHealthCheckConfigured):
  1300. project.up()
  1301. containers = project.containers()
  1302. assert len(containers) == 1
  1303. svc1 = project.get_service('svc1')
  1304. svc2 = project.get_service('svc2')
  1305. assert 'svc1' in svc2.get_dependency_names()
  1306. with pytest.raises(NoHealthCheckConfigured):
  1307. svc1.is_healthy()