project_test.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import random
  4. import py
  5. import pytest
  6. from docker.errors import NotFound
  7. from .. import mock
  8. from ..helpers import build_config
  9. from .testcases import DockerClientTestCase
  10. from compose.config import config
  11. from compose.config import ConfigurationError
  12. from compose.config.config import V2_0
  13. from compose.config.types import VolumeFromSpec
  14. from compose.config.types import VolumeSpec
  15. from compose.const import LABEL_PROJECT
  16. from compose.const import LABEL_SERVICE
  17. from compose.container import Container
  18. from compose.project import Project
  19. from compose.project import ProjectError
  20. from compose.service import ConvergenceStrategy
  21. from tests.integration.testcases import v2_only
  22. class ProjectTest(DockerClientTestCase):
  23. def test_containers(self):
  24. web = self.create_service('web')
  25. db = self.create_service('db')
  26. project = Project('composetest', [web, db], self.client)
  27. project.up()
  28. containers = project.containers()
  29. self.assertEqual(len(containers), 2)
  30. def test_containers_with_service_names(self):
  31. web = self.create_service('web')
  32. db = self.create_service('db')
  33. project = Project('composetest', [web, db], self.client)
  34. project.up()
  35. containers = project.containers(['web'])
  36. self.assertEqual(
  37. [c.name for c in containers],
  38. ['composetest_web_1'])
  39. def test_containers_with_extra_service(self):
  40. web = self.create_service('web')
  41. web_1 = web.create_container()
  42. db = self.create_service('db')
  43. db_1 = db.create_container()
  44. self.create_service('extra').create_container()
  45. project = Project('composetest', [web, db], self.client)
  46. self.assertEqual(
  47. set(project.containers(stopped=True)),
  48. set([web_1, db_1]),
  49. )
  50. def test_volumes_from_service(self):
  51. project = Project.from_config(
  52. name='composetest',
  53. config_data=build_config({
  54. 'data': {
  55. 'image': 'busybox:latest',
  56. 'volumes': ['/var/data'],
  57. },
  58. 'db': {
  59. 'image': 'busybox:latest',
  60. 'volumes_from': ['data'],
  61. },
  62. }),
  63. client=self.client,
  64. )
  65. db = project.get_service('db')
  66. data = project.get_service('data')
  67. self.assertEqual(db.volumes_from, [VolumeFromSpec(data, 'rw', 'service')])
  68. def test_volumes_from_container(self):
  69. data_container = Container.create(
  70. self.client,
  71. image='busybox:latest',
  72. volumes=['/var/data'],
  73. name='composetest_data_container',
  74. labels={LABEL_PROJECT: 'composetest'},
  75. )
  76. project = Project.from_config(
  77. name='composetest',
  78. config_data=build_config({
  79. 'db': {
  80. 'image': 'busybox:latest',
  81. 'volumes_from': ['composetest_data_container'],
  82. },
  83. }),
  84. client=self.client,
  85. )
  86. db = project.get_service('db')
  87. self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw'])
  88. @v2_only()
  89. def test_network_mode_from_service(self):
  90. project = Project.from_config(
  91. name='composetest',
  92. client=self.client,
  93. config_data=build_config({
  94. 'version': V2_0,
  95. 'services': {
  96. 'net': {
  97. 'image': 'busybox:latest',
  98. 'command': ["top"]
  99. },
  100. 'web': {
  101. 'image': 'busybox:latest',
  102. 'network_mode': 'service:net',
  103. 'command': ["top"]
  104. },
  105. },
  106. }),
  107. )
  108. project.up()
  109. web = project.get_service('web')
  110. net = project.get_service('net')
  111. self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
  112. @v2_only()
  113. def test_network_mode_from_container(self):
  114. def get_project():
  115. return Project.from_config(
  116. name='composetest',
  117. config_data=build_config({
  118. 'version': V2_0,
  119. 'services': {
  120. 'web': {
  121. 'image': 'busybox:latest',
  122. 'network_mode': 'container:composetest_net_container'
  123. },
  124. },
  125. }),
  126. client=self.client,
  127. )
  128. with pytest.raises(ConfigurationError) as excinfo:
  129. get_project()
  130. assert "container 'composetest_net_container' which does not exist" in excinfo.exconly()
  131. net_container = Container.create(
  132. self.client,
  133. image='busybox:latest',
  134. name='composetest_net_container',
  135. command='top',
  136. labels={LABEL_PROJECT: 'composetest'},
  137. )
  138. net_container.start()
  139. project = get_project()
  140. project.up()
  141. web = project.get_service('web')
  142. self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
  143. def test_net_from_service_v1(self):
  144. project = Project.from_config(
  145. name='composetest',
  146. config_data=build_config({
  147. 'net': {
  148. 'image': 'busybox:latest',
  149. 'command': ["top"]
  150. },
  151. 'web': {
  152. 'image': 'busybox:latest',
  153. 'net': 'container:net',
  154. 'command': ["top"]
  155. },
  156. }),
  157. client=self.client,
  158. )
  159. project.up()
  160. web = project.get_service('web')
  161. net = project.get_service('net')
  162. self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
  163. def test_net_from_container_v1(self):
  164. def get_project():
  165. return Project.from_config(
  166. name='composetest',
  167. config_data=build_config({
  168. 'web': {
  169. 'image': 'busybox:latest',
  170. 'net': 'container:composetest_net_container'
  171. },
  172. }),
  173. client=self.client,
  174. )
  175. with pytest.raises(ConfigurationError) as excinfo:
  176. get_project()
  177. assert "container 'composetest_net_container' which does not exist" in excinfo.exconly()
  178. net_container = Container.create(
  179. self.client,
  180. image='busybox:latest',
  181. name='composetest_net_container',
  182. command='top',
  183. labels={LABEL_PROJECT: 'composetest'},
  184. )
  185. net_container.start()
  186. project = get_project()
  187. project.up()
  188. web = project.get_service('web')
  189. self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
  190. def test_start_pause_unpause_stop_kill_remove(self):
  191. web = self.create_service('web')
  192. db = self.create_service('db')
  193. project = Project('composetest', [web, db], self.client)
  194. project.start()
  195. self.assertEqual(len(web.containers()), 0)
  196. self.assertEqual(len(db.containers()), 0)
  197. web_container_1 = web.create_container()
  198. web_container_2 = web.create_container()
  199. db_container = db.create_container()
  200. project.start(service_names=['web'])
  201. self.assertEqual(
  202. set(c.name for c in project.containers()),
  203. set([web_container_1.name, web_container_2.name]))
  204. project.start()
  205. self.assertEqual(
  206. set(c.name for c in project.containers()),
  207. set([web_container_1.name, web_container_2.name, db_container.name]))
  208. project.pause(service_names=['web'])
  209. self.assertEqual(
  210. set([c.name for c in project.containers() if c.is_paused]),
  211. set([web_container_1.name, web_container_2.name]))
  212. project.pause()
  213. self.assertEqual(
  214. set([c.name for c in project.containers() if c.is_paused]),
  215. set([web_container_1.name, web_container_2.name, db_container.name]))
  216. project.unpause(service_names=['db'])
  217. self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 2)
  218. project.unpause()
  219. self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 0)
  220. project.stop(service_names=['web'], timeout=1)
  221. self.assertEqual(set(c.name for c in project.containers()), set([db_container.name]))
  222. project.kill(service_names=['db'])
  223. self.assertEqual(len(project.containers()), 0)
  224. self.assertEqual(len(project.containers(stopped=True)), 3)
  225. project.remove_stopped(service_names=['web'])
  226. self.assertEqual(len(project.containers(stopped=True)), 1)
  227. project.remove_stopped()
  228. self.assertEqual(len(project.containers(stopped=True)), 0)
  229. def test_create(self):
  230. web = self.create_service('web')
  231. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  232. project = Project('composetest', [web, db], self.client)
  233. project.create(['db'])
  234. self.assertEqual(len(project.containers()), 0)
  235. self.assertEqual(len(project.containers(stopped=True)), 1)
  236. self.assertEqual(len(db.containers()), 0)
  237. self.assertEqual(len(db.containers(stopped=True)), 1)
  238. self.assertEqual(len(web.containers(stopped=True)), 0)
  239. def test_create_twice(self):
  240. web = self.create_service('web')
  241. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  242. project = Project('composetest', [web, db], self.client)
  243. project.create(['db', 'web'])
  244. project.create(['db', 'web'])
  245. self.assertEqual(len(project.containers()), 0)
  246. self.assertEqual(len(project.containers(stopped=True)), 2)
  247. self.assertEqual(len(db.containers()), 0)
  248. self.assertEqual(len(db.containers(stopped=True)), 1)
  249. self.assertEqual(len(web.containers()), 0)
  250. self.assertEqual(len(web.containers(stopped=True)), 1)
  251. def test_create_with_links(self):
  252. db = self.create_service('db')
  253. web = self.create_service('web', links=[(db, 'db')])
  254. project = Project('composetest', [db, web], self.client)
  255. project.create(['web'])
  256. self.assertEqual(len(project.containers()), 0)
  257. self.assertEqual(len(project.containers(stopped=True)), 2)
  258. self.assertEqual(len(db.containers()), 0)
  259. self.assertEqual(len(db.containers(stopped=True)), 1)
  260. self.assertEqual(len(web.containers()), 0)
  261. self.assertEqual(len(web.containers(stopped=True)), 1)
  262. def test_create_strategy_always(self):
  263. db = self.create_service('db')
  264. project = Project('composetest', [db], self.client)
  265. project.create(['db'])
  266. old_id = project.containers(stopped=True)[0].id
  267. project.create(['db'], strategy=ConvergenceStrategy.always)
  268. self.assertEqual(len(project.containers()), 0)
  269. self.assertEqual(len(project.containers(stopped=True)), 1)
  270. db_container = project.containers(stopped=True)[0]
  271. self.assertNotEqual(db_container.id, old_id)
  272. def test_create_strategy_never(self):
  273. db = self.create_service('db')
  274. project = Project('composetest', [db], self.client)
  275. project.create(['db'])
  276. old_id = project.containers(stopped=True)[0].id
  277. project.create(['db'], strategy=ConvergenceStrategy.never)
  278. self.assertEqual(len(project.containers()), 0)
  279. self.assertEqual(len(project.containers(stopped=True)), 1)
  280. db_container = project.containers(stopped=True)[0]
  281. self.assertEqual(db_container.id, old_id)
  282. def test_project_up(self):
  283. web = self.create_service('web')
  284. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  285. project = Project('composetest', [web, db], self.client)
  286. project.start()
  287. self.assertEqual(len(project.containers()), 0)
  288. project.up(['db'])
  289. self.assertEqual(len(project.containers()), 1)
  290. self.assertEqual(len(db.containers()), 1)
  291. self.assertEqual(len(web.containers()), 0)
  292. def test_project_up_starts_uncreated_services(self):
  293. db = self.create_service('db')
  294. web = self.create_service('web', links=[(db, 'db')])
  295. project = Project('composetest', [db, web], self.client)
  296. project.up(['db'])
  297. self.assertEqual(len(project.containers()), 1)
  298. project.up()
  299. self.assertEqual(len(project.containers()), 2)
  300. self.assertEqual(len(db.containers()), 1)
  301. self.assertEqual(len(web.containers()), 1)
  302. def test_recreate_preserves_volumes(self):
  303. web = self.create_service('web')
  304. db = self.create_service('db', volumes=[VolumeSpec.parse('/etc')])
  305. project = Project('composetest', [web, db], self.client)
  306. project.start()
  307. self.assertEqual(len(project.containers()), 0)
  308. project.up(['db'])
  309. self.assertEqual(len(project.containers()), 1)
  310. old_db_id = project.containers()[0].id
  311. db_volume_path = project.containers()[0].get('Volumes./etc')
  312. project.up(strategy=ConvergenceStrategy.always)
  313. self.assertEqual(len(project.containers()), 2)
  314. db_container = [c for c in project.containers() if 'db' in c.name][0]
  315. self.assertNotEqual(db_container.id, old_db_id)
  316. self.assertEqual(db_container.get('Volumes./etc'), db_volume_path)
  317. def test_project_up_with_no_recreate_running(self):
  318. web = self.create_service('web')
  319. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  320. project = Project('composetest', [web, db], self.client)
  321. project.start()
  322. self.assertEqual(len(project.containers()), 0)
  323. project.up(['db'])
  324. self.assertEqual(len(project.containers()), 1)
  325. old_db_id = project.containers()[0].id
  326. container, = project.containers()
  327. db_volume_path = container.get_mount('/var/db')['Source']
  328. project.up(strategy=ConvergenceStrategy.never)
  329. self.assertEqual(len(project.containers()), 2)
  330. db_container = [c for c in project.containers() if 'db' in c.name][0]
  331. self.assertEqual(db_container.id, old_db_id)
  332. self.assertEqual(
  333. db_container.get_mount('/var/db')['Source'],
  334. db_volume_path)
  335. def test_project_up_with_no_recreate_stopped(self):
  336. web = self.create_service('web')
  337. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  338. project = Project('composetest', [web, db], self.client)
  339. project.start()
  340. self.assertEqual(len(project.containers()), 0)
  341. project.up(['db'])
  342. project.kill()
  343. old_containers = project.containers(stopped=True)
  344. self.assertEqual(len(old_containers), 1)
  345. old_container, = old_containers
  346. old_db_id = old_container.id
  347. db_volume_path = old_container.get_mount('/var/db')['Source']
  348. project.up(strategy=ConvergenceStrategy.never)
  349. new_containers = project.containers(stopped=True)
  350. self.assertEqual(len(new_containers), 2)
  351. self.assertEqual([c.is_running for c in new_containers], [True, True])
  352. db_container = [c for c in new_containers if 'db' in c.name][0]
  353. self.assertEqual(db_container.id, old_db_id)
  354. self.assertEqual(
  355. db_container.get_mount('/var/db')['Source'],
  356. db_volume_path)
  357. def test_project_up_without_all_services(self):
  358. console = self.create_service('console')
  359. db = self.create_service('db')
  360. project = Project('composetest', [console, db], self.client)
  361. project.start()
  362. self.assertEqual(len(project.containers()), 0)
  363. project.up()
  364. self.assertEqual(len(project.containers()), 2)
  365. self.assertEqual(len(db.containers()), 1)
  366. self.assertEqual(len(console.containers()), 1)
  367. def test_project_up_starts_links(self):
  368. console = self.create_service('console')
  369. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  370. web = self.create_service('web', links=[(db, 'db')])
  371. project = Project('composetest', [web, db, console], self.client)
  372. project.start()
  373. self.assertEqual(len(project.containers()), 0)
  374. project.up(['web'])
  375. self.assertEqual(len(project.containers()), 2)
  376. self.assertEqual(len(web.containers()), 1)
  377. self.assertEqual(len(db.containers()), 1)
  378. self.assertEqual(len(console.containers()), 0)
  379. def test_project_up_starts_depends(self):
  380. project = Project.from_config(
  381. name='composetest',
  382. config_data=build_config({
  383. 'console': {
  384. 'image': 'busybox:latest',
  385. 'command': ["top"],
  386. },
  387. 'data': {
  388. 'image': 'busybox:latest',
  389. 'command': ["top"]
  390. },
  391. 'db': {
  392. 'image': 'busybox:latest',
  393. 'command': ["top"],
  394. 'volumes_from': ['data'],
  395. },
  396. 'web': {
  397. 'image': 'busybox:latest',
  398. 'command': ["top"],
  399. 'links': ['db'],
  400. },
  401. }),
  402. client=self.client,
  403. )
  404. project.start()
  405. self.assertEqual(len(project.containers()), 0)
  406. project.up(['web'])
  407. self.assertEqual(len(project.containers()), 3)
  408. self.assertEqual(len(project.get_service('web').containers()), 1)
  409. self.assertEqual(len(project.get_service('db').containers()), 1)
  410. self.assertEqual(len(project.get_service('data').containers()), 1)
  411. self.assertEqual(len(project.get_service('console').containers()), 0)
  412. def test_project_up_with_no_deps(self):
  413. project = Project.from_config(
  414. name='composetest',
  415. config_data=build_config({
  416. 'console': {
  417. 'image': 'busybox:latest',
  418. 'command': ["top"],
  419. },
  420. 'data': {
  421. 'image': 'busybox:latest',
  422. 'command': ["top"]
  423. },
  424. 'db': {
  425. 'image': 'busybox:latest',
  426. 'command': ["top"],
  427. 'volumes_from': ['data'],
  428. },
  429. 'web': {
  430. 'image': 'busybox:latest',
  431. 'command': ["top"],
  432. 'links': ['db'],
  433. },
  434. }),
  435. client=self.client,
  436. )
  437. project.start()
  438. self.assertEqual(len(project.containers()), 0)
  439. project.up(['db'], start_deps=False)
  440. self.assertEqual(len(project.containers(stopped=True)), 2)
  441. self.assertEqual(len(project.get_service('web').containers()), 0)
  442. self.assertEqual(len(project.get_service('db').containers()), 1)
  443. self.assertEqual(len(project.get_service('data').containers()), 0)
  444. self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
  445. self.assertEqual(len(project.get_service('console').containers()), 0)
  446. def test_unscale_after_restart(self):
  447. web = self.create_service('web')
  448. project = Project('composetest', [web], self.client)
  449. project.start()
  450. service = project.get_service('web')
  451. service.scale(1)
  452. self.assertEqual(len(service.containers()), 1)
  453. service.scale(3)
  454. self.assertEqual(len(service.containers()), 3)
  455. project.up()
  456. service = project.get_service('web')
  457. self.assertEqual(len(service.containers()), 3)
  458. service.scale(1)
  459. self.assertEqual(len(service.containers()), 1)
  460. project.up()
  461. service = project.get_service('web')
  462. self.assertEqual(len(service.containers()), 1)
  463. # does scale=0 ,makes any sense? after recreating at least 1 container is running
  464. service.scale(0)
  465. project.up()
  466. service = project.get_service('web')
  467. self.assertEqual(len(service.containers()), 1)
  468. @v2_only()
  469. def test_project_up_networks(self):
  470. config_data = config.Config(
  471. version=V2_0,
  472. services=[{
  473. 'name': 'web',
  474. 'image': 'busybox:latest',
  475. 'command': 'top',
  476. 'networks': {
  477. 'foo': None,
  478. 'bar': None,
  479. 'baz': {'aliases': ['extra']},
  480. },
  481. }],
  482. volumes={},
  483. networks={
  484. 'foo': {'driver': 'bridge'},
  485. 'bar': {'driver': None},
  486. 'baz': {},
  487. },
  488. )
  489. project = Project.from_config(
  490. client=self.client,
  491. name='composetest',
  492. config_data=config_data,
  493. )
  494. project.up()
  495. containers = project.containers()
  496. assert len(containers) == 1
  497. container, = containers
  498. for net_name in ['foo', 'bar', 'baz']:
  499. full_net_name = 'composetest_{}'.format(net_name)
  500. network_data = self.client.inspect_network(full_net_name)
  501. assert network_data['Name'] == full_net_name
  502. aliases_key = 'NetworkSettings.Networks.{net}.Aliases'
  503. assert 'web' in container.get(aliases_key.format(net='composetest_foo'))
  504. assert 'web' in container.get(aliases_key.format(net='composetest_baz'))
  505. assert 'extra' in container.get(aliases_key.format(net='composetest_baz'))
  506. foo_data = self.client.inspect_network('composetest_foo')
  507. assert foo_data['Driver'] == 'bridge'
  508. @v2_only()
  509. def test_up_with_ipam_config(self):
  510. config_data = config.Config(
  511. version=V2_0,
  512. services=[{
  513. 'name': 'web',
  514. 'image': 'busybox:latest',
  515. 'networks': {'front': None},
  516. }],
  517. volumes={},
  518. networks={
  519. 'front': {
  520. 'driver': 'bridge',
  521. 'driver_opts': {
  522. "com.docker.network.bridge.enable_icc": "false",
  523. },
  524. 'ipam': {
  525. 'driver': 'default',
  526. 'config': [{
  527. "subnet": "172.28.0.0/16",
  528. "ip_range": "172.28.5.0/24",
  529. "gateway": "172.28.5.254",
  530. "aux_addresses": {
  531. "a": "172.28.1.5",
  532. "b": "172.28.1.6",
  533. "c": "172.28.1.7",
  534. },
  535. }],
  536. },
  537. },
  538. },
  539. )
  540. project = Project.from_config(
  541. client=self.client,
  542. name='composetest',
  543. config_data=config_data,
  544. )
  545. project.up()
  546. network = self.client.networks(names=['composetest_front'])[0]
  547. assert network['Options'] == {
  548. "com.docker.network.bridge.enable_icc": "false"
  549. }
  550. assert network['IPAM'] == {
  551. 'Driver': 'default',
  552. 'Options': None,
  553. 'Config': [{
  554. 'Subnet': "172.28.0.0/16",
  555. 'IPRange': "172.28.5.0/24",
  556. 'Gateway': "172.28.5.254",
  557. 'AuxiliaryAddresses': {
  558. 'a': '172.28.1.5',
  559. 'b': '172.28.1.6',
  560. 'c': '172.28.1.7',
  561. },
  562. }],
  563. }
  564. @v2_only()
  565. def test_up_with_network_static_addresses(self):
  566. config_data = config.Config(
  567. version=V2_0,
  568. services=[{
  569. 'name': 'web',
  570. 'image': 'busybox:latest',
  571. 'command': 'top',
  572. 'networks': {
  573. 'static_test': {
  574. 'ipv4_address': '172.16.100.100',
  575. 'ipv6_address': 'fe80::1001:102'
  576. }
  577. },
  578. }],
  579. volumes={},
  580. networks={
  581. 'static_test': {
  582. 'driver': 'bridge',
  583. 'driver_opts': {
  584. "com.docker.network.enable_ipv6": "true",
  585. },
  586. 'ipam': {
  587. 'driver': 'default',
  588. 'config': [
  589. {"subnet": "172.16.100.0/24",
  590. "gateway": "172.16.100.1"},
  591. {"subnet": "fe80::/64",
  592. "gateway": "fe80::1001:1"}
  593. ]
  594. }
  595. }
  596. }
  597. )
  598. project = Project.from_config(
  599. client=self.client,
  600. name='composetest',
  601. config_data=config_data,
  602. )
  603. project.up(detached=True)
  604. network = self.client.networks(names=['static_test'])[0]
  605. service_container = project.get_service('web').containers()[0]
  606. assert network['Options'] == {
  607. "com.docker.network.enable_ipv6": "true"
  608. }
  609. IPAMConfig = (service_container.inspect().get('NetworkSettings', {}).
  610. get('Networks', {}).get('composetest_static_test', {}).
  611. get('IPAMConfig', {}))
  612. assert IPAMConfig.get('IPv4Address') == '172.16.100.100'
  613. assert IPAMConfig.get('IPv6Address') == 'fe80::1001:102'
  614. @v2_only()
  615. def test_up_with_network_static_addresses_missing_subnet(self):
  616. config_data = config.Config(
  617. version=V2_0,
  618. services=[{
  619. 'name': 'web',
  620. 'image': 'busybox:latest',
  621. 'networks': {
  622. 'static_test': {
  623. 'ipv4_address': '172.16.100.100',
  624. 'ipv6_address': 'fe80::1001:101'
  625. }
  626. },
  627. }],
  628. volumes={},
  629. networks={
  630. 'static_test': {
  631. 'driver': 'bridge',
  632. 'driver_opts': {
  633. "com.docker.network.enable_ipv6": "true",
  634. },
  635. 'ipam': {
  636. 'driver': 'default',
  637. },
  638. },
  639. },
  640. )
  641. project = Project.from_config(
  642. client=self.client,
  643. name='composetest',
  644. config_data=config_data,
  645. )
  646. with self.assertRaises(ProjectError):
  647. project.up()
  648. @v2_only()
  649. def test_project_up_with_network_internal(self):
  650. self.require_api_version('1.23')
  651. config_data = config.Config(
  652. version=V2_0,
  653. services=[{
  654. 'name': 'web',
  655. 'image': 'busybox:latest',
  656. 'networks': {'internal': None},
  657. }],
  658. volumes={},
  659. networks={
  660. 'internal': {'driver': 'bridge', 'internal': True},
  661. },
  662. )
  663. project = Project.from_config(
  664. client=self.client,
  665. name='composetest',
  666. config_data=config_data,
  667. )
  668. project.up()
  669. network = self.client.networks(names=['composetest_internal'])[0]
  670. assert network['Internal'] is True
  671. @v2_only()
  672. def test_project_up_volumes(self):
  673. vol_name = '{0:x}'.format(random.getrandbits(32))
  674. full_vol_name = 'composetest_{0}'.format(vol_name)
  675. config_data = config.Config(
  676. version=V2_0,
  677. services=[{
  678. 'name': 'web',
  679. 'image': 'busybox:latest',
  680. 'command': 'top'
  681. }],
  682. volumes={vol_name: {'driver': 'local'}},
  683. networks={},
  684. )
  685. project = Project.from_config(
  686. name='composetest',
  687. config_data=config_data, client=self.client
  688. )
  689. project.up()
  690. self.assertEqual(len(project.containers()), 1)
  691. volume_data = self.client.inspect_volume(full_vol_name)
  692. self.assertEqual(volume_data['Name'], full_vol_name)
  693. self.assertEqual(volume_data['Driver'], 'local')
  694. @v2_only()
  695. def test_project_up_logging_with_multiple_files(self):
  696. base_file = config.ConfigFile(
  697. 'base.yml',
  698. {
  699. 'version': V2_0,
  700. 'services': {
  701. 'simple': {'image': 'busybox:latest', 'command': 'top'},
  702. 'another': {
  703. 'image': 'busybox:latest',
  704. 'command': 'top',
  705. 'logging': {
  706. 'driver': "json-file",
  707. 'options': {
  708. 'max-size': "10m"
  709. }
  710. }
  711. }
  712. }
  713. })
  714. override_file = config.ConfigFile(
  715. 'override.yml',
  716. {
  717. 'version': V2_0,
  718. 'services': {
  719. 'another': {
  720. 'logging': {
  721. 'driver': "none"
  722. }
  723. }
  724. }
  725. })
  726. details = config.ConfigDetails('.', [base_file, override_file])
  727. tmpdir = py.test.ensuretemp('logging_test')
  728. self.addCleanup(tmpdir.remove)
  729. with tmpdir.as_cwd():
  730. config_data = config.load(details)
  731. project = Project.from_config(
  732. name='composetest', config_data=config_data, client=self.client
  733. )
  734. project.up()
  735. containers = project.containers()
  736. self.assertEqual(len(containers), 2)
  737. another = project.get_service('another').containers()[0]
  738. log_config = another.get('HostConfig.LogConfig')
  739. self.assertTrue(log_config)
  740. self.assertEqual(log_config.get('Type'), 'none')
  741. @v2_only()
  742. def test_project_up_port_mappings_with_multiple_files(self):
  743. base_file = config.ConfigFile(
  744. 'base.yml',
  745. {
  746. 'version': V2_0,
  747. 'services': {
  748. 'simple': {
  749. 'image': 'busybox:latest',
  750. 'command': 'top',
  751. 'ports': ['1234:1234']
  752. },
  753. },
  754. })
  755. override_file = config.ConfigFile(
  756. 'override.yml',
  757. {
  758. 'version': V2_0,
  759. 'services': {
  760. 'simple': {
  761. 'ports': ['1234:1234']
  762. }
  763. }
  764. })
  765. details = config.ConfigDetails('.', [base_file, override_file])
  766. config_data = config.load(details)
  767. project = Project.from_config(
  768. name='composetest', config_data=config_data, client=self.client
  769. )
  770. project.up()
  771. containers = project.containers()
  772. self.assertEqual(len(containers), 1)
  773. @v2_only()
  774. def test_initialize_volumes(self):
  775. vol_name = '{0:x}'.format(random.getrandbits(32))
  776. full_vol_name = 'composetest_{0}'.format(vol_name)
  777. config_data = config.Config(
  778. version=V2_0,
  779. services=[{
  780. 'name': 'web',
  781. 'image': 'busybox:latest',
  782. 'command': 'top'
  783. }],
  784. volumes={vol_name: {}},
  785. networks={},
  786. )
  787. project = Project.from_config(
  788. name='composetest',
  789. config_data=config_data, client=self.client
  790. )
  791. project.volumes.initialize()
  792. volume_data = self.client.inspect_volume(full_vol_name)
  793. self.assertEqual(volume_data['Name'], full_vol_name)
  794. self.assertEqual(volume_data['Driver'], 'local')
  795. @v2_only()
  796. def test_project_up_implicit_volume_driver(self):
  797. vol_name = '{0:x}'.format(random.getrandbits(32))
  798. full_vol_name = 'composetest_{0}'.format(vol_name)
  799. config_data = config.Config(
  800. version=V2_0,
  801. services=[{
  802. 'name': 'web',
  803. 'image': 'busybox:latest',
  804. 'command': 'top'
  805. }],
  806. volumes={vol_name: {}},
  807. networks={},
  808. )
  809. project = Project.from_config(
  810. name='composetest',
  811. config_data=config_data, client=self.client
  812. )
  813. project.up()
  814. volume_data = self.client.inspect_volume(full_vol_name)
  815. self.assertEqual(volume_data['Name'], full_vol_name)
  816. self.assertEqual(volume_data['Driver'], 'local')
  817. @v2_only()
  818. def test_initialize_volumes_invalid_volume_driver(self):
  819. vol_name = '{0:x}'.format(random.getrandbits(32))
  820. config_data = config.Config(
  821. version=V2_0,
  822. services=[{
  823. 'name': 'web',
  824. 'image': 'busybox:latest',
  825. 'command': 'top'
  826. }],
  827. volumes={vol_name: {'driver': 'foobar'}},
  828. networks={},
  829. )
  830. project = Project.from_config(
  831. name='composetest',
  832. config_data=config_data, client=self.client
  833. )
  834. with self.assertRaises(config.ConfigurationError):
  835. project.volumes.initialize()
  836. @v2_only()
  837. def test_initialize_volumes_updated_driver(self):
  838. vol_name = '{0:x}'.format(random.getrandbits(32))
  839. full_vol_name = 'composetest_{0}'.format(vol_name)
  840. config_data = config.Config(
  841. version=V2_0,
  842. services=[{
  843. 'name': 'web',
  844. 'image': 'busybox:latest',
  845. 'command': 'top'
  846. }],
  847. volumes={vol_name: {'driver': 'local'}},
  848. networks={},
  849. )
  850. project = Project.from_config(
  851. name='composetest',
  852. config_data=config_data, client=self.client
  853. )
  854. project.volumes.initialize()
  855. volume_data = self.client.inspect_volume(full_vol_name)
  856. self.assertEqual(volume_data['Name'], full_vol_name)
  857. self.assertEqual(volume_data['Driver'], 'local')
  858. config_data = config_data._replace(
  859. volumes={vol_name: {'driver': 'smb'}}
  860. )
  861. project = Project.from_config(
  862. name='composetest',
  863. config_data=config_data,
  864. client=self.client
  865. )
  866. with self.assertRaises(config.ConfigurationError) as e:
  867. project.volumes.initialize()
  868. assert 'Configuration for volume {0} specifies driver smb'.format(
  869. vol_name
  870. ) in str(e.exception)
  871. @v2_only()
  872. def test_initialize_volumes_updated_blank_driver(self):
  873. vol_name = '{0:x}'.format(random.getrandbits(32))
  874. full_vol_name = 'composetest_{0}'.format(vol_name)
  875. config_data = config.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. networks={},
  884. )
  885. project = Project.from_config(
  886. name='composetest',
  887. config_data=config_data, client=self.client
  888. )
  889. project.volumes.initialize()
  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. config_data = config_data._replace(
  894. volumes={vol_name: {}}
  895. )
  896. project = Project.from_config(
  897. name='composetest',
  898. config_data=config_data,
  899. client=self.client
  900. )
  901. project.volumes.initialize()
  902. volume_data = self.client.inspect_volume(full_vol_name)
  903. self.assertEqual(volume_data['Name'], full_vol_name)
  904. self.assertEqual(volume_data['Driver'], 'local')
  905. @v2_only()
  906. def test_initialize_volumes_external_volumes(self):
  907. # Use composetest_ prefix so it gets garbage-collected in tearDown()
  908. vol_name = 'composetest_{0:x}'.format(random.getrandbits(32))
  909. full_vol_name = 'composetest_{0}'.format(vol_name)
  910. self.client.create_volume(vol_name)
  911. config_data = config.Config(
  912. version=V2_0,
  913. services=[{
  914. 'name': 'web',
  915. 'image': 'busybox:latest',
  916. 'command': 'top'
  917. }],
  918. volumes={
  919. vol_name: {'external': True, 'external_name': vol_name}
  920. },
  921. networks=None,
  922. )
  923. project = Project.from_config(
  924. name='composetest',
  925. config_data=config_data, client=self.client
  926. )
  927. project.volumes.initialize()
  928. with self.assertRaises(NotFound):
  929. self.client.inspect_volume(full_vol_name)
  930. @v2_only()
  931. def test_initialize_volumes_inexistent_external_volume(self):
  932. vol_name = '{0:x}'.format(random.getrandbits(32))
  933. config_data = config.Config(
  934. version=V2_0,
  935. services=[{
  936. 'name': 'web',
  937. 'image': 'busybox:latest',
  938. 'command': 'top'
  939. }],
  940. volumes={
  941. vol_name: {'external': True, 'external_name': vol_name}
  942. },
  943. networks=None,
  944. )
  945. project = Project.from_config(
  946. name='composetest',
  947. config_data=config_data, client=self.client
  948. )
  949. with self.assertRaises(config.ConfigurationError) as e:
  950. project.volumes.initialize()
  951. assert 'Volume {0} declared as external'.format(
  952. vol_name
  953. ) in str(e.exception)
  954. @v2_only()
  955. def test_project_up_named_volumes_in_binds(self):
  956. vol_name = '{0:x}'.format(random.getrandbits(32))
  957. full_vol_name = 'composetest_{0}'.format(vol_name)
  958. base_file = config.ConfigFile(
  959. 'base.yml',
  960. {
  961. 'version': V2_0,
  962. 'services': {
  963. 'simple': {
  964. 'image': 'busybox:latest',
  965. 'command': 'top',
  966. 'volumes': ['{0}:/data'.format(vol_name)]
  967. },
  968. },
  969. 'volumes': {
  970. vol_name: {'driver': 'local'}
  971. }
  972. })
  973. config_details = config.ConfigDetails('.', [base_file])
  974. config_data = config.load(config_details)
  975. project = Project.from_config(
  976. name='composetest', config_data=config_data, client=self.client
  977. )
  978. service = project.services[0]
  979. self.assertEqual(service.name, 'simple')
  980. volumes = service.options.get('volumes')
  981. self.assertEqual(len(volumes), 1)
  982. self.assertEqual(volumes[0].external, full_vol_name)
  983. project.up()
  984. engine_volumes = self.client.volumes()['Volumes']
  985. container = service.get_container()
  986. assert [mount['Name'] for mount in container.get('Mounts')] == [full_vol_name]
  987. assert next((v for v in engine_volumes if v['Name'] == vol_name), None) is None
  988. def test_project_up_orphans(self):
  989. config_dict = {
  990. 'service1': {
  991. 'image': 'busybox:latest',
  992. 'command': 'top',
  993. }
  994. }
  995. config_data = build_config(config_dict)
  996. project = Project.from_config(
  997. name='composetest', config_data=config_data, client=self.client
  998. )
  999. project.up()
  1000. config_dict['service2'] = config_dict['service1']
  1001. del config_dict['service1']
  1002. config_data = build_config(config_dict)
  1003. project = Project.from_config(
  1004. name='composetest', config_data=config_data, client=self.client
  1005. )
  1006. with mock.patch('compose.project.log') as mock_log:
  1007. project.up()
  1008. mock_log.warning.assert_called_once_with(mock.ANY)
  1009. assert len([
  1010. ctnr for ctnr in project._labeled_containers()
  1011. if ctnr.labels.get(LABEL_SERVICE) == 'service1'
  1012. ]) == 1
  1013. project.up(remove_orphans=True)
  1014. assert len([
  1015. ctnr for ctnr in project._labeled_containers()
  1016. if ctnr.labels.get(LABEL_SERVICE) == 'service1'
  1017. ]) == 0