project_test.py 36 KB

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