project_test.py 53 KB

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