project_test.py 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583
  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_unscale_after_restart(self):
  464. web = self.create_service('web')
  465. project = Project('composetest', [web], self.client)
  466. project.start()
  467. service = project.get_service('web')
  468. service.scale(1)
  469. self.assertEqual(len(service.containers()), 1)
  470. service.scale(3)
  471. self.assertEqual(len(service.containers()), 3)
  472. project.up()
  473. service = project.get_service('web')
  474. self.assertEqual(len(service.containers()), 1)
  475. service.scale(1)
  476. self.assertEqual(len(service.containers()), 1)
  477. project.up(scale_override={'web': 3})
  478. service = project.get_service('web')
  479. self.assertEqual(len(service.containers()), 3)
  480. # does scale=0 ,makes any sense? after recreating at least 1 container is running
  481. service.scale(0)
  482. project.up()
  483. service = project.get_service('web')
  484. self.assertEqual(len(service.containers()), 1)
  485. @v2_only()
  486. def test_project_up_networks(self):
  487. config_data = build_config(
  488. version=V2_0,
  489. services=[{
  490. 'name': 'web',
  491. 'image': 'busybox:latest',
  492. 'command': 'top',
  493. 'networks': {
  494. 'foo': None,
  495. 'bar': None,
  496. 'baz': {'aliases': ['extra']},
  497. },
  498. }],
  499. networks={
  500. 'foo': {'driver': 'bridge'},
  501. 'bar': {'driver': None},
  502. 'baz': {},
  503. },
  504. )
  505. project = Project.from_config(
  506. client=self.client,
  507. name='composetest',
  508. config_data=config_data,
  509. )
  510. project.up()
  511. containers = project.containers()
  512. assert len(containers) == 1
  513. container, = containers
  514. for net_name in ['foo', 'bar', 'baz']:
  515. full_net_name = 'composetest_{}'.format(net_name)
  516. network_data = self.client.inspect_network(full_net_name)
  517. assert network_data['Name'] == full_net_name
  518. aliases_key = 'NetworkSettings.Networks.{net}.Aliases'
  519. assert 'web' in container.get(aliases_key.format(net='composetest_foo'))
  520. assert 'web' in container.get(aliases_key.format(net='composetest_baz'))
  521. assert 'extra' in container.get(aliases_key.format(net='composetest_baz'))
  522. foo_data = self.client.inspect_network('composetest_foo')
  523. assert foo_data['Driver'] == 'bridge'
  524. @v2_only()
  525. def test_up_with_ipam_config(self):
  526. config_data = build_config(
  527. version=V2_0,
  528. services=[{
  529. 'name': 'web',
  530. 'image': 'busybox:latest',
  531. 'networks': {'front': None},
  532. }],
  533. networks={
  534. 'front': {
  535. 'driver': 'bridge',
  536. 'driver_opts': {
  537. "com.docker.network.bridge.enable_icc": "false",
  538. },
  539. 'ipam': {
  540. 'driver': 'default',
  541. 'config': [{
  542. "subnet": "172.28.0.0/16",
  543. "ip_range": "172.28.5.0/24",
  544. "gateway": "172.28.5.254",
  545. "aux_addresses": {
  546. "a": "172.28.1.5",
  547. "b": "172.28.1.6",
  548. "c": "172.28.1.7",
  549. },
  550. }],
  551. },
  552. },
  553. },
  554. )
  555. project = Project.from_config(
  556. client=self.client,
  557. name='composetest',
  558. config_data=config_data,
  559. )
  560. project.up()
  561. network = self.client.networks(names=['composetest_front'])[0]
  562. assert network['Options'] == {
  563. "com.docker.network.bridge.enable_icc": "false"
  564. }
  565. assert network['IPAM'] == {
  566. 'Driver': 'default',
  567. 'Options': None,
  568. 'Config': [{
  569. 'Subnet': "172.28.0.0/16",
  570. 'IPRange': "172.28.5.0/24",
  571. 'Gateway': "172.28.5.254",
  572. 'AuxiliaryAddresses': {
  573. 'a': '172.28.1.5',
  574. 'b': '172.28.1.6',
  575. 'c': '172.28.1.7',
  576. },
  577. }],
  578. }
  579. @v2_only()
  580. def test_up_with_ipam_options(self):
  581. config_data = build_config(
  582. version=V2_0,
  583. services=[{
  584. 'name': 'web',
  585. 'image': 'busybox:latest',
  586. 'networks': {'front': None},
  587. }],
  588. networks={
  589. 'front': {
  590. 'driver': 'bridge',
  591. 'ipam': {
  592. 'driver': 'default',
  593. 'options': {
  594. "com.docker.compose.network.test": "9-29-045"
  595. }
  596. },
  597. },
  598. },
  599. )
  600. project = Project.from_config(
  601. client=self.client,
  602. name='composetest',
  603. config_data=config_data,
  604. )
  605. project.up()
  606. network = self.client.networks(names=['composetest_front'])[0]
  607. assert network['IPAM']['Options'] == {
  608. "com.docker.compose.network.test": "9-29-045"
  609. }
  610. @v2_only()
  611. def test_up_with_network_static_addresses(self):
  612. config_data = build_config(
  613. version=V2_0,
  614. services=[{
  615. 'name': 'web',
  616. 'image': 'busybox:latest',
  617. 'command': 'top',
  618. 'networks': {
  619. 'static_test': {
  620. 'ipv4_address': '172.16.100.100',
  621. 'ipv6_address': 'fe80::1001:102'
  622. }
  623. },
  624. }],
  625. networks={
  626. 'static_test': {
  627. 'driver': 'bridge',
  628. 'driver_opts': {
  629. "com.docker.network.enable_ipv6": "true",
  630. },
  631. 'ipam': {
  632. 'driver': 'default',
  633. 'config': [
  634. {"subnet": "172.16.100.0/24",
  635. "gateway": "172.16.100.1"},
  636. {"subnet": "fe80::/64",
  637. "gateway": "fe80::1001:1"}
  638. ]
  639. }
  640. }
  641. }
  642. )
  643. project = Project.from_config(
  644. client=self.client,
  645. name='composetest',
  646. config_data=config_data,
  647. )
  648. project.up(detached=True)
  649. network = self.client.networks(names=['static_test'])[0]
  650. service_container = project.get_service('web').containers()[0]
  651. assert network['Options'] == {
  652. "com.docker.network.enable_ipv6": "true"
  653. }
  654. IPAMConfig = (service_container.inspect().get('NetworkSettings', {}).
  655. get('Networks', {}).get('composetest_static_test', {}).
  656. get('IPAMConfig', {}))
  657. assert IPAMConfig.get('IPv4Address') == '172.16.100.100'
  658. assert IPAMConfig.get('IPv6Address') == 'fe80::1001:102'
  659. @v2_1_only()
  660. def test_up_with_enable_ipv6(self):
  661. self.require_api_version('1.23')
  662. config_data = build_config(
  663. version=V2_0,
  664. services=[{
  665. 'name': 'web',
  666. 'image': 'busybox:latest',
  667. 'command': 'top',
  668. 'networks': {
  669. 'static_test': {
  670. 'ipv6_address': 'fe80::1001:102'
  671. }
  672. },
  673. }],
  674. networks={
  675. 'static_test': {
  676. 'driver': 'bridge',
  677. 'enable_ipv6': True,
  678. 'ipam': {
  679. 'driver': 'default',
  680. 'config': [
  681. {"subnet": "fe80::/64",
  682. "gateway": "fe80::1001:1"}
  683. ]
  684. }
  685. }
  686. }
  687. )
  688. project = Project.from_config(
  689. client=self.client,
  690. name='composetest',
  691. config_data=config_data,
  692. )
  693. project.up(detached=True)
  694. network = self.client.networks(names=['static_test'])[0]
  695. service_container = project.get_service('web').containers()[0]
  696. assert network['EnableIPv6'] is True
  697. ipam_config = (service_container.inspect().get('NetworkSettings', {}).
  698. get('Networks', {}).get('composetest_static_test', {}).
  699. get('IPAMConfig', {}))
  700. assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
  701. @v2_only()
  702. def test_up_with_network_static_addresses_missing_subnet(self):
  703. config_data = build_config(
  704. version=V2_0,
  705. services=[{
  706. 'name': 'web',
  707. 'image': 'busybox:latest',
  708. 'networks': {
  709. 'static_test': {
  710. 'ipv4_address': '172.16.100.100',
  711. 'ipv6_address': 'fe80::1001:101'
  712. }
  713. },
  714. }],
  715. networks={
  716. 'static_test': {
  717. 'driver': 'bridge',
  718. 'driver_opts': {
  719. "com.docker.network.enable_ipv6": "true",
  720. },
  721. 'ipam': {
  722. 'driver': 'default',
  723. },
  724. },
  725. },
  726. )
  727. project = Project.from_config(
  728. client=self.client,
  729. name='composetest',
  730. config_data=config_data,
  731. )
  732. with self.assertRaises(ProjectError):
  733. project.up()
  734. @v2_1_only()
  735. def test_up_with_network_link_local_ips(self):
  736. config_data = build_config(
  737. version=V2_1,
  738. services=[{
  739. 'name': 'web',
  740. 'image': 'busybox:latest',
  741. 'networks': {
  742. 'linklocaltest': {
  743. 'link_local_ips': ['169.254.8.8']
  744. }
  745. }
  746. }],
  747. networks={
  748. 'linklocaltest': {'driver': 'bridge'}
  749. }
  750. )
  751. project = Project.from_config(
  752. client=self.client,
  753. name='composetest',
  754. config_data=config_data
  755. )
  756. project.up(detached=True)
  757. service_container = project.get_service('web').containers(stopped=True)[0]
  758. ipam_config = service_container.inspect().get(
  759. 'NetworkSettings', {}
  760. ).get(
  761. 'Networks', {}
  762. ).get(
  763. 'composetest_linklocaltest', {}
  764. ).get('IPAMConfig', {})
  765. assert 'LinkLocalIPs' in ipam_config
  766. assert ipam_config['LinkLocalIPs'] == ['169.254.8.8']
  767. @v2_1_only()
  768. def test_up_with_isolation(self):
  769. self.require_api_version('1.24')
  770. config_data = build_config(
  771. version=V2_1,
  772. services=[{
  773. 'name': 'web',
  774. 'image': 'busybox:latest',
  775. 'isolation': 'default'
  776. }],
  777. )
  778. project = Project.from_config(
  779. client=self.client,
  780. name='composetest',
  781. config_data=config_data
  782. )
  783. project.up(detached=True)
  784. service_container = project.get_service('web').containers(stopped=True)[0]
  785. assert service_container.inspect()['HostConfig']['Isolation'] == 'default'
  786. @v2_1_only()
  787. def test_up_with_invalid_isolation(self):
  788. self.require_api_version('1.24')
  789. config_data = build_config(
  790. version=V2_1,
  791. services=[{
  792. 'name': 'web',
  793. 'image': 'busybox:latest',
  794. 'isolation': 'foobar'
  795. }],
  796. )
  797. project = Project.from_config(
  798. client=self.client,
  799. name='composetest',
  800. config_data=config_data
  801. )
  802. with self.assertRaises(ProjectError):
  803. project.up()
  804. @v2_only()
  805. def test_project_up_with_network_internal(self):
  806. self.require_api_version('1.23')
  807. config_data = build_config(
  808. version=V2_0,
  809. services=[{
  810. 'name': 'web',
  811. 'image': 'busybox:latest',
  812. 'networks': {'internal': None},
  813. }],
  814. networks={
  815. 'internal': {'driver': 'bridge', 'internal': True},
  816. },
  817. )
  818. project = Project.from_config(
  819. client=self.client,
  820. name='composetest',
  821. config_data=config_data,
  822. )
  823. project.up()
  824. network = self.client.networks(names=['composetest_internal'])[0]
  825. assert network['Internal'] is True
  826. @v2_1_only()
  827. def test_project_up_with_network_label(self):
  828. self.require_api_version('1.23')
  829. network_name = 'network_with_label'
  830. config_data = build_config(
  831. version=V2_0,
  832. services=[{
  833. 'name': 'web',
  834. 'image': 'busybox:latest',
  835. 'networks': {network_name: None}
  836. }],
  837. networks={
  838. network_name: {'labels': {'label_key': 'label_val'}}
  839. }
  840. )
  841. project = Project.from_config(
  842. client=self.client,
  843. name='composetest',
  844. config_data=config_data
  845. )
  846. project.up()
  847. networks = [
  848. n for n in self.client.networks()
  849. if n['Name'].startswith('composetest_')
  850. ]
  851. assert [n['Name'] for n in networks] == ['composetest_{}'.format(network_name)]
  852. assert 'label_key' in networks[0]['Labels']
  853. assert networks[0]['Labels']['label_key'] == 'label_val'
  854. @v2_only()
  855. def test_project_up_volumes(self):
  856. vol_name = '{0:x}'.format(random.getrandbits(32))
  857. full_vol_name = 'composetest_{0}'.format(vol_name)
  858. config_data = build_config(
  859. version=V2_0,
  860. services=[{
  861. 'name': 'web',
  862. 'image': 'busybox:latest',
  863. 'command': 'top'
  864. }],
  865. volumes={vol_name: {'driver': 'local'}},
  866. )
  867. project = Project.from_config(
  868. name='composetest',
  869. config_data=config_data, client=self.client
  870. )
  871. project.up()
  872. self.assertEqual(len(project.containers()), 1)
  873. volume_data = self.client.inspect_volume(full_vol_name)
  874. self.assertEqual(volume_data['Name'], full_vol_name)
  875. self.assertEqual(volume_data['Driver'], 'local')
  876. @v2_1_only()
  877. def test_project_up_with_volume_labels(self):
  878. self.require_api_version('1.23')
  879. volume_name = 'volume_with_label'
  880. config_data = build_config(
  881. version=V2_0,
  882. services=[{
  883. 'name': 'web',
  884. 'image': 'busybox:latest',
  885. 'volumes': [VolumeSpec.parse('{}:/data'.format(volume_name))]
  886. }],
  887. volumes={
  888. volume_name: {
  889. 'labels': {
  890. 'label_key': 'label_val'
  891. }
  892. }
  893. },
  894. )
  895. project = Project.from_config(
  896. client=self.client,
  897. name='composetest',
  898. config_data=config_data,
  899. )
  900. project.up()
  901. volumes = [
  902. v for v in self.client.volumes().get('Volumes', [])
  903. if v['Name'].startswith('composetest_')
  904. ]
  905. assert [v['Name'] for v in volumes] == ['composetest_{}'.format(volume_name)]
  906. assert 'label_key' in volumes[0]['Labels']
  907. assert volumes[0]['Labels']['label_key'] == 'label_val'
  908. @v2_only()
  909. def test_project_up_logging_with_multiple_files(self):
  910. base_file = config.ConfigFile(
  911. 'base.yml',
  912. {
  913. 'version': V2_0,
  914. 'services': {
  915. 'simple': {'image': 'busybox:latest', 'command': 'top'},
  916. 'another': {
  917. 'image': 'busybox:latest',
  918. 'command': 'top',
  919. 'logging': {
  920. 'driver': "json-file",
  921. 'options': {
  922. 'max-size': "10m"
  923. }
  924. }
  925. }
  926. }
  927. })
  928. override_file = config.ConfigFile(
  929. 'override.yml',
  930. {
  931. 'version': V2_0,
  932. 'services': {
  933. 'another': {
  934. 'logging': {
  935. 'driver': "none"
  936. }
  937. }
  938. }
  939. })
  940. details = config.ConfigDetails('.', [base_file, override_file])
  941. tmpdir = py.test.ensuretemp('logging_test')
  942. self.addCleanup(tmpdir.remove)
  943. with tmpdir.as_cwd():
  944. config_data = config.load(details)
  945. project = Project.from_config(
  946. name='composetest', config_data=config_data, client=self.client
  947. )
  948. project.up()
  949. containers = project.containers()
  950. self.assertEqual(len(containers), 2)
  951. another = project.get_service('another').containers()[0]
  952. log_config = another.get('HostConfig.LogConfig')
  953. self.assertTrue(log_config)
  954. self.assertEqual(log_config.get('Type'), 'none')
  955. @v2_only()
  956. def test_project_up_port_mappings_with_multiple_files(self):
  957. base_file = config.ConfigFile(
  958. 'base.yml',
  959. {
  960. 'version': V2_0,
  961. 'services': {
  962. 'simple': {
  963. 'image': 'busybox:latest',
  964. 'command': 'top',
  965. 'ports': ['1234:1234']
  966. },
  967. },
  968. })
  969. override_file = config.ConfigFile(
  970. 'override.yml',
  971. {
  972. 'version': V2_0,
  973. 'services': {
  974. 'simple': {
  975. 'ports': ['1234:1234']
  976. }
  977. }
  978. })
  979. details = config.ConfigDetails('.', [base_file, override_file])
  980. config_data = config.load(details)
  981. project = Project.from_config(
  982. name='composetest', config_data=config_data, client=self.client
  983. )
  984. project.up()
  985. containers = project.containers()
  986. self.assertEqual(len(containers), 1)
  987. def test_project_up_config_scale(self):
  988. config_data = build_config(
  989. version=V2_2,
  990. services=[{
  991. 'name': 'web',
  992. 'image': 'busybox:latest',
  993. 'command': 'top',
  994. 'scale': 3
  995. }]
  996. )
  997. project = Project.from_config(
  998. name='composetest', config_data=config_data, client=self.client
  999. )
  1000. project.up()
  1001. assert len(project.containers()) == 3
  1002. project.up(scale_override={'web': 2})
  1003. assert len(project.containers()) == 2
  1004. project.up(scale_override={'web': 4})
  1005. assert len(project.containers()) == 4
  1006. project.stop()
  1007. project.up()
  1008. assert len(project.containers()) == 3
  1009. @v2_only()
  1010. def test_initialize_volumes(self):
  1011. vol_name = '{0:x}'.format(random.getrandbits(32))
  1012. full_vol_name = 'composetest_{0}'.format(vol_name)
  1013. config_data = build_config(
  1014. version=V2_0,
  1015. services=[{
  1016. 'name': 'web',
  1017. 'image': 'busybox:latest',
  1018. 'command': 'top'
  1019. }],
  1020. volumes={vol_name: {}},
  1021. )
  1022. project = Project.from_config(
  1023. name='composetest',
  1024. config_data=config_data, client=self.client
  1025. )
  1026. project.volumes.initialize()
  1027. volume_data = self.client.inspect_volume(full_vol_name)
  1028. assert volume_data['Name'] == full_vol_name
  1029. assert volume_data['Driver'] == 'local'
  1030. @v2_only()
  1031. def test_project_up_implicit_volume_driver(self):
  1032. vol_name = '{0:x}'.format(random.getrandbits(32))
  1033. full_vol_name = 'composetest_{0}'.format(vol_name)
  1034. config_data = build_config(
  1035. version=V2_0,
  1036. services=[{
  1037. 'name': 'web',
  1038. 'image': 'busybox:latest',
  1039. 'command': 'top'
  1040. }],
  1041. volumes={vol_name: {}},
  1042. )
  1043. project = Project.from_config(
  1044. name='composetest',
  1045. config_data=config_data, client=self.client
  1046. )
  1047. project.up()
  1048. volume_data = self.client.inspect_volume(full_vol_name)
  1049. self.assertEqual(volume_data['Name'], full_vol_name)
  1050. self.assertEqual(volume_data['Driver'], 'local')
  1051. @v3_only()
  1052. def test_project_up_with_secrets(self):
  1053. create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
  1054. config_data = build_config(
  1055. version=V3_1,
  1056. services=[{
  1057. 'name': 'web',
  1058. 'image': 'busybox:latest',
  1059. 'command': 'cat /run/secrets/special',
  1060. 'secrets': [
  1061. types.ServiceSecret.parse({'source': 'super', 'target': 'special'}),
  1062. ],
  1063. }],
  1064. secrets={
  1065. 'super': {
  1066. 'file': os.path.abspath('tests/fixtures/secrets/default'),
  1067. },
  1068. },
  1069. )
  1070. project = Project.from_config(
  1071. client=self.client,
  1072. name='composetest',
  1073. config_data=config_data,
  1074. )
  1075. project.up()
  1076. project.stop()
  1077. containers = project.containers(stopped=True)
  1078. assert len(containers) == 1
  1079. container, = containers
  1080. output = container.logs()
  1081. assert output == b"This is the secret\n"
  1082. @v2_only()
  1083. def test_initialize_volumes_invalid_volume_driver(self):
  1084. vol_name = '{0:x}'.format(random.getrandbits(32))
  1085. config_data = build_config(
  1086. version=V2_0,
  1087. services=[{
  1088. 'name': 'web',
  1089. 'image': 'busybox:latest',
  1090. 'command': 'top'
  1091. }],
  1092. volumes={vol_name: {'driver': 'foobar'}},
  1093. )
  1094. project = Project.from_config(
  1095. name='composetest',
  1096. config_data=config_data, client=self.client
  1097. )
  1098. with self.assertRaises(config.ConfigurationError):
  1099. project.volumes.initialize()
  1100. @v2_only()
  1101. def test_initialize_volumes_updated_driver(self):
  1102. vol_name = '{0:x}'.format(random.getrandbits(32))
  1103. full_vol_name = 'composetest_{0}'.format(vol_name)
  1104. config_data = build_config(
  1105. version=V2_0,
  1106. services=[{
  1107. 'name': 'web',
  1108. 'image': 'busybox:latest',
  1109. 'command': 'top'
  1110. }],
  1111. volumes={vol_name: {'driver': 'local'}},
  1112. )
  1113. project = Project.from_config(
  1114. name='composetest',
  1115. config_data=config_data, client=self.client
  1116. )
  1117. project.volumes.initialize()
  1118. volume_data = self.client.inspect_volume(full_vol_name)
  1119. self.assertEqual(volume_data['Name'], full_vol_name)
  1120. self.assertEqual(volume_data['Driver'], 'local')
  1121. config_data = config_data._replace(
  1122. volumes={vol_name: {'driver': 'smb'}}
  1123. )
  1124. project = Project.from_config(
  1125. name='composetest',
  1126. config_data=config_data,
  1127. client=self.client
  1128. )
  1129. with self.assertRaises(config.ConfigurationError) as e:
  1130. project.volumes.initialize()
  1131. assert 'Configuration for volume {0} specifies driver smb'.format(
  1132. vol_name
  1133. ) in str(e.exception)
  1134. @v2_only()
  1135. def test_initialize_volumes_updated_blank_driver(self):
  1136. vol_name = '{0:x}'.format(random.getrandbits(32))
  1137. full_vol_name = 'composetest_{0}'.format(vol_name)
  1138. config_data = build_config(
  1139. version=V2_0,
  1140. services=[{
  1141. 'name': 'web',
  1142. 'image': 'busybox:latest',
  1143. 'command': 'top'
  1144. }],
  1145. volumes={vol_name: {'driver': 'local'}},
  1146. )
  1147. project = Project.from_config(
  1148. name='composetest',
  1149. config_data=config_data, client=self.client
  1150. )
  1151. project.volumes.initialize()
  1152. volume_data = self.client.inspect_volume(full_vol_name)
  1153. self.assertEqual(volume_data['Name'], full_vol_name)
  1154. self.assertEqual(volume_data['Driver'], 'local')
  1155. config_data = config_data._replace(
  1156. volumes={vol_name: {}}
  1157. )
  1158. project = Project.from_config(
  1159. name='composetest',
  1160. config_data=config_data,
  1161. client=self.client
  1162. )
  1163. project.volumes.initialize()
  1164. volume_data = self.client.inspect_volume(full_vol_name)
  1165. self.assertEqual(volume_data['Name'], full_vol_name)
  1166. self.assertEqual(volume_data['Driver'], 'local')
  1167. @v2_only()
  1168. def test_initialize_volumes_external_volumes(self):
  1169. # Use composetest_ prefix so it gets garbage-collected in tearDown()
  1170. vol_name = 'composetest_{0:x}'.format(random.getrandbits(32))
  1171. full_vol_name = 'composetest_{0}'.format(vol_name)
  1172. self.client.create_volume(vol_name)
  1173. config_data = build_config(
  1174. version=V2_0,
  1175. services=[{
  1176. 'name': 'web',
  1177. 'image': 'busybox:latest',
  1178. 'command': 'top'
  1179. }],
  1180. volumes={
  1181. vol_name: {'external': True, 'external_name': vol_name}
  1182. },
  1183. )
  1184. project = Project.from_config(
  1185. name='composetest',
  1186. config_data=config_data, client=self.client
  1187. )
  1188. project.volumes.initialize()
  1189. with self.assertRaises(NotFound):
  1190. self.client.inspect_volume(full_vol_name)
  1191. @v2_only()
  1192. def test_initialize_volumes_inexistent_external_volume(self):
  1193. vol_name = '{0:x}'.format(random.getrandbits(32))
  1194. config_data = build_config(
  1195. version=V2_0,
  1196. services=[{
  1197. 'name': 'web',
  1198. 'image': 'busybox:latest',
  1199. 'command': 'top'
  1200. }],
  1201. volumes={
  1202. vol_name: {'external': True, 'external_name': vol_name}
  1203. },
  1204. )
  1205. project = Project.from_config(
  1206. name='composetest',
  1207. config_data=config_data, client=self.client
  1208. )
  1209. with self.assertRaises(config.ConfigurationError) as e:
  1210. project.volumes.initialize()
  1211. assert 'Volume {0} declared as external'.format(
  1212. vol_name
  1213. ) in str(e.exception)
  1214. @v2_only()
  1215. def test_project_up_named_volumes_in_binds(self):
  1216. vol_name = '{0:x}'.format(random.getrandbits(32))
  1217. full_vol_name = 'composetest_{0}'.format(vol_name)
  1218. base_file = config.ConfigFile(
  1219. 'base.yml',
  1220. {
  1221. 'version': V2_0,
  1222. 'services': {
  1223. 'simple': {
  1224. 'image': 'busybox:latest',
  1225. 'command': 'top',
  1226. 'volumes': ['{0}:/data'.format(vol_name)]
  1227. },
  1228. },
  1229. 'volumes': {
  1230. vol_name: {'driver': 'local'}
  1231. }
  1232. })
  1233. config_details = config.ConfigDetails('.', [base_file])
  1234. config_data = config.load(config_details)
  1235. project = Project.from_config(
  1236. name='composetest', config_data=config_data, client=self.client
  1237. )
  1238. service = project.services[0]
  1239. self.assertEqual(service.name, 'simple')
  1240. volumes = service.options.get('volumes')
  1241. self.assertEqual(len(volumes), 1)
  1242. self.assertEqual(volumes[0].external, full_vol_name)
  1243. project.up()
  1244. engine_volumes = self.client.volumes()['Volumes']
  1245. container = service.get_container()
  1246. assert [mount['Name'] for mount in container.get('Mounts')] == [full_vol_name]
  1247. assert next((v for v in engine_volumes if v['Name'] == vol_name), None) is None
  1248. def test_project_up_orphans(self):
  1249. config_dict = {
  1250. 'service1': {
  1251. 'image': 'busybox:latest',
  1252. 'command': 'top',
  1253. }
  1254. }
  1255. config_data = load_config(config_dict)
  1256. project = Project.from_config(
  1257. name='composetest', config_data=config_data, client=self.client
  1258. )
  1259. project.up()
  1260. config_dict['service2'] = config_dict['service1']
  1261. del config_dict['service1']
  1262. config_data = load_config(config_dict)
  1263. project = Project.from_config(
  1264. name='composetest', config_data=config_data, client=self.client
  1265. )
  1266. with mock.patch('compose.project.log') as mock_log:
  1267. project.up()
  1268. mock_log.warning.assert_called_once_with(mock.ANY)
  1269. assert len([
  1270. ctnr for ctnr in project._labeled_containers()
  1271. if ctnr.labels.get(LABEL_SERVICE) == 'service1'
  1272. ]) == 1
  1273. project.up(remove_orphans=True)
  1274. assert len([
  1275. ctnr for ctnr in project._labeled_containers()
  1276. if ctnr.labels.get(LABEL_SERVICE) == 'service1'
  1277. ]) == 0
  1278. @v2_1_only()
  1279. def test_project_up_healthy_dependency(self):
  1280. config_dict = {
  1281. 'version': '2.1',
  1282. 'services': {
  1283. 'svc1': {
  1284. 'image': 'busybox:latest',
  1285. 'command': 'top',
  1286. 'healthcheck': {
  1287. 'test': 'exit 0',
  1288. 'retries': 1,
  1289. 'timeout': '10s',
  1290. 'interval': '1s'
  1291. },
  1292. },
  1293. 'svc2': {
  1294. 'image': 'busybox:latest',
  1295. 'command': 'top',
  1296. 'depends_on': {
  1297. 'svc1': {'condition': 'service_healthy'},
  1298. }
  1299. }
  1300. }
  1301. }
  1302. config_data = load_config(config_dict)
  1303. project = Project.from_config(
  1304. name='composetest', config_data=config_data, client=self.client
  1305. )
  1306. project.up()
  1307. containers = project.containers()
  1308. assert len(containers) == 2
  1309. svc1 = project.get_service('svc1')
  1310. svc2 = project.get_service('svc2')
  1311. assert 'svc1' in svc2.get_dependency_names()
  1312. assert svc1.is_healthy()
  1313. @v2_1_only()
  1314. def test_project_up_unhealthy_dependency(self):
  1315. config_dict = {
  1316. 'version': '2.1',
  1317. 'services': {
  1318. 'svc1': {
  1319. 'image': 'busybox:latest',
  1320. 'command': 'top',
  1321. 'healthcheck': {
  1322. 'test': 'exit 1',
  1323. 'retries': 1,
  1324. 'timeout': '10s',
  1325. 'interval': '1s'
  1326. },
  1327. },
  1328. 'svc2': {
  1329. 'image': 'busybox:latest',
  1330. 'command': 'top',
  1331. 'depends_on': {
  1332. 'svc1': {'condition': 'service_healthy'},
  1333. }
  1334. }
  1335. }
  1336. }
  1337. config_data = load_config(config_dict)
  1338. project = Project.from_config(
  1339. name='composetest', config_data=config_data, client=self.client
  1340. )
  1341. with pytest.raises(ProjectError):
  1342. project.up()
  1343. containers = project.containers()
  1344. assert len(containers) == 1
  1345. svc1 = project.get_service('svc1')
  1346. svc2 = project.get_service('svc2')
  1347. assert 'svc1' in svc2.get_dependency_names()
  1348. with pytest.raises(HealthCheckFailed):
  1349. svc1.is_healthy()
  1350. @v2_1_only()
  1351. def test_project_up_no_healthcheck_dependency(self):
  1352. config_dict = {
  1353. 'version': '2.1',
  1354. 'services': {
  1355. 'svc1': {
  1356. 'image': 'busybox:latest',
  1357. 'command': 'top',
  1358. 'healthcheck': {
  1359. 'disable': True
  1360. },
  1361. },
  1362. 'svc2': {
  1363. 'image': 'busybox:latest',
  1364. 'command': 'top',
  1365. 'depends_on': {
  1366. 'svc1': {'condition': 'service_healthy'},
  1367. }
  1368. }
  1369. }
  1370. }
  1371. config_data = load_config(config_dict)
  1372. project = Project.from_config(
  1373. name='composetest', config_data=config_data, client=self.client
  1374. )
  1375. with pytest.raises(ProjectError):
  1376. project.up()
  1377. containers = project.containers()
  1378. assert len(containers) == 1
  1379. svc1 = project.get_service('svc1')
  1380. svc2 = project.get_service('svc2')
  1381. assert 'svc1' in svc2.get_dependency_names()
  1382. with pytest.raises(NoHealthCheckConfigured):
  1383. svc1.is_healthy()