project_test.py 38 KB

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