project_test.py 53 KB

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