1
0

project_test.py 55 KB

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