project_test.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import random
  4. from .testcases import DockerClientTestCase
  5. from compose.cli.docker_client import docker_client
  6. from compose.config import config
  7. from compose.config.types import VolumeFromSpec
  8. from compose.config.types import VolumeSpec
  9. from compose.const import LABEL_PROJECT
  10. from compose.container import Container
  11. from compose.project import Project
  12. from compose.service import ConvergenceStrategy
  13. from compose.service import Net
  14. def build_service_dicts(service_config):
  15. return config.load(
  16. config.ConfigDetails(
  17. 'working_dir',
  18. [config.ConfigFile(None, service_config)]))
  19. class ProjectTest(DockerClientTestCase):
  20. def test_containers(self):
  21. web = self.create_service('web')
  22. db = self.create_service('db')
  23. project = Project('composetest', [web, db], self.client)
  24. project.up()
  25. containers = project.containers()
  26. self.assertEqual(len(containers), 2)
  27. def test_containers_with_service_names(self):
  28. web = self.create_service('web')
  29. db = self.create_service('db')
  30. project = Project('composetest', [web, db], self.client)
  31. project.up()
  32. containers = project.containers(['web'])
  33. self.assertEqual(
  34. [c.name for c in containers],
  35. ['composetest_web_1'])
  36. def test_containers_with_extra_service(self):
  37. web = self.create_service('web')
  38. web_1 = web.create_container()
  39. db = self.create_service('db')
  40. db_1 = db.create_container()
  41. self.create_service('extra').create_container()
  42. project = Project('composetest', [web, db], self.client)
  43. self.assertEqual(
  44. set(project.containers(stopped=True)),
  45. set([web_1, db_1]),
  46. )
  47. def test_volumes_from_service(self):
  48. service_dicts = build_service_dicts({
  49. 'data': {
  50. 'image': 'busybox:latest',
  51. 'volumes': ['/var/data'],
  52. },
  53. 'db': {
  54. 'image': 'busybox:latest',
  55. 'volumes_from': ['data'],
  56. },
  57. })
  58. project = Project.from_config(
  59. name='composetest',
  60. config_data=service_dicts,
  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')])
  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_service_dicts({
  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. def test_get_network_does_not_exist(self):
  87. self.require_api_version('1.21')
  88. client = docker_client(version='1.21')
  89. project = Project('composetest', [], client)
  90. assert project.get_network() is None
  91. def test_get_network(self):
  92. self.require_api_version('1.21')
  93. client = docker_client(version='1.21')
  94. network_name = 'network_does_exist'
  95. project = Project(network_name, [], client)
  96. client.create_network(network_name)
  97. self.addCleanup(client.remove_network, network_name)
  98. assert project.get_network()['Name'] == network_name
  99. def test_net_from_service(self):
  100. project = Project.from_config(
  101. name='composetest',
  102. config_data=build_service_dicts({
  103. 'net': {
  104. 'image': 'busybox:latest',
  105. 'command': ["top"]
  106. },
  107. 'web': {
  108. 'image': 'busybox:latest',
  109. 'net': 'container:net',
  110. 'command': ["top"]
  111. },
  112. }),
  113. client=self.client,
  114. )
  115. project.up()
  116. web = project.get_service('web')
  117. net = project.get_service('net')
  118. self.assertEqual(web.net.mode, 'container:' + net.containers()[0].id)
  119. def test_net_from_container(self):
  120. net_container = Container.create(
  121. self.client,
  122. image='busybox:latest',
  123. name='composetest_net_container',
  124. command='top',
  125. labels={LABEL_PROJECT: 'composetest'},
  126. )
  127. net_container.start()
  128. project = Project.from_config(
  129. name='composetest',
  130. config_data=build_service_dicts({
  131. 'web': {
  132. 'image': 'busybox:latest',
  133. 'net': 'container:composetest_net_container'
  134. },
  135. }),
  136. client=self.client,
  137. )
  138. project.up()
  139. web = project.get_service('web')
  140. self.assertEqual(web.net.mode, 'container:' + net_container.id)
  141. def test_start_pause_unpause_stop_kill_remove(self):
  142. web = self.create_service('web')
  143. db = self.create_service('db')
  144. project = Project('composetest', [web, db], self.client)
  145. project.start()
  146. self.assertEqual(len(web.containers()), 0)
  147. self.assertEqual(len(db.containers()), 0)
  148. web_container_1 = web.create_container()
  149. web_container_2 = web.create_container()
  150. db_container = db.create_container()
  151. project.start(service_names=['web'])
  152. self.assertEqual(set(c.name for c in project.containers()), set([web_container_1.name, web_container_2.name]))
  153. project.start()
  154. self.assertEqual(set(c.name for c in project.containers()),
  155. set([web_container_1.name, web_container_2.name, db_container.name]))
  156. project.pause(service_names=['web'])
  157. self.assertEqual(set([c.name for c in project.containers() if c.is_paused]),
  158. set([web_container_1.name, web_container_2.name]))
  159. project.pause()
  160. self.assertEqual(set([c.name for c in project.containers() if c.is_paused]),
  161. set([web_container_1.name, web_container_2.name, db_container.name]))
  162. project.unpause(service_names=['db'])
  163. self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 2)
  164. project.unpause()
  165. self.assertEqual(len([c.name for c in project.containers() if c.is_paused]), 0)
  166. project.stop(service_names=['web'], timeout=1)
  167. self.assertEqual(set(c.name for c in project.containers()), set([db_container.name]))
  168. project.kill(service_names=['db'])
  169. self.assertEqual(len(project.containers()), 0)
  170. self.assertEqual(len(project.containers(stopped=True)), 3)
  171. project.remove_stopped(service_names=['web'])
  172. self.assertEqual(len(project.containers(stopped=True)), 1)
  173. project.remove_stopped()
  174. self.assertEqual(len(project.containers(stopped=True)), 0)
  175. def test_create(self):
  176. web = self.create_service('web')
  177. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  178. project = Project('composetest', [web, db], self.client)
  179. project.create(['db'])
  180. self.assertEqual(len(project.containers()), 0)
  181. self.assertEqual(len(project.containers(stopped=True)), 1)
  182. self.assertEqual(len(db.containers()), 0)
  183. self.assertEqual(len(db.containers(stopped=True)), 1)
  184. self.assertEqual(len(web.containers(stopped=True)), 0)
  185. def test_create_twice(self):
  186. web = self.create_service('web')
  187. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  188. project = Project('composetest', [web, db], self.client)
  189. project.create(['db', 'web'])
  190. project.create(['db', 'web'])
  191. self.assertEqual(len(project.containers()), 0)
  192. self.assertEqual(len(project.containers(stopped=True)), 2)
  193. self.assertEqual(len(db.containers()), 0)
  194. self.assertEqual(len(db.containers(stopped=True)), 1)
  195. self.assertEqual(len(web.containers()), 0)
  196. self.assertEqual(len(web.containers(stopped=True)), 1)
  197. def test_create_with_links(self):
  198. db = self.create_service('db')
  199. web = self.create_service('web', links=[(db, 'db')])
  200. project = Project('composetest', [db, web], self.client)
  201. project.create(['web'])
  202. self.assertEqual(len(project.containers()), 0)
  203. self.assertEqual(len(project.containers(stopped=True)), 2)
  204. self.assertEqual(len(db.containers()), 0)
  205. self.assertEqual(len(db.containers(stopped=True)), 1)
  206. self.assertEqual(len(web.containers()), 0)
  207. self.assertEqual(len(web.containers(stopped=True)), 1)
  208. def test_create_strategy_always(self):
  209. db = self.create_service('db')
  210. project = Project('composetest', [db], self.client)
  211. project.create(['db'])
  212. old_id = project.containers(stopped=True)[0].id
  213. project.create(['db'], strategy=ConvergenceStrategy.always)
  214. self.assertEqual(len(project.containers()), 0)
  215. self.assertEqual(len(project.containers(stopped=True)), 1)
  216. db_container = project.containers(stopped=True)[0]
  217. self.assertNotEqual(db_container.id, old_id)
  218. def test_create_strategy_never(self):
  219. db = self.create_service('db')
  220. project = Project('composetest', [db], self.client)
  221. project.create(['db'])
  222. old_id = project.containers(stopped=True)[0].id
  223. project.create(['db'], strategy=ConvergenceStrategy.never)
  224. self.assertEqual(len(project.containers()), 0)
  225. self.assertEqual(len(project.containers(stopped=True)), 1)
  226. db_container = project.containers(stopped=True)[0]
  227. self.assertEqual(db_container.id, old_id)
  228. def test_project_up(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.start()
  233. self.assertEqual(len(project.containers()), 0)
  234. project.up(['db'])
  235. self.assertEqual(len(project.containers()), 1)
  236. self.assertEqual(len(db.containers()), 1)
  237. self.assertEqual(len(web.containers()), 0)
  238. def test_project_up_starts_uncreated_services(self):
  239. db = self.create_service('db')
  240. web = self.create_service('web', links=[(db, 'db')])
  241. project = Project('composetest', [db, web], self.client)
  242. project.up(['db'])
  243. self.assertEqual(len(project.containers()), 1)
  244. project.up()
  245. self.assertEqual(len(project.containers()), 2)
  246. self.assertEqual(len(db.containers()), 1)
  247. self.assertEqual(len(web.containers()), 1)
  248. def test_recreate_preserves_volumes(self):
  249. web = self.create_service('web')
  250. db = self.create_service('db', volumes=[VolumeSpec.parse('/etc')])
  251. project = Project('composetest', [web, db], self.client)
  252. project.start()
  253. self.assertEqual(len(project.containers()), 0)
  254. project.up(['db'])
  255. self.assertEqual(len(project.containers()), 1)
  256. old_db_id = project.containers()[0].id
  257. db_volume_path = project.containers()[0].get('Volumes./etc')
  258. project.up(strategy=ConvergenceStrategy.always)
  259. self.assertEqual(len(project.containers()), 2)
  260. db_container = [c for c in project.containers() if 'db' in c.name][0]
  261. self.assertNotEqual(db_container.id, old_db_id)
  262. self.assertEqual(db_container.get('Volumes./etc'), db_volume_path)
  263. def test_project_up_with_no_recreate_running(self):
  264. web = self.create_service('web')
  265. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  266. project = Project('composetest', [web, db], self.client)
  267. project.start()
  268. self.assertEqual(len(project.containers()), 0)
  269. project.up(['db'])
  270. self.assertEqual(len(project.containers()), 1)
  271. old_db_id = project.containers()[0].id
  272. container, = project.containers()
  273. db_volume_path = container.get_mount('/var/db')['Source']
  274. project.up(strategy=ConvergenceStrategy.never)
  275. self.assertEqual(len(project.containers()), 2)
  276. db_container = [c for c in project.containers() if 'db' in c.name][0]
  277. self.assertEqual(db_container.id, old_db_id)
  278. self.assertEqual(
  279. db_container.get_mount('/var/db')['Source'],
  280. db_volume_path)
  281. def test_project_up_with_no_recreate_stopped(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. project.kill()
  289. old_containers = project.containers(stopped=True)
  290. self.assertEqual(len(old_containers), 1)
  291. old_container, = old_containers
  292. old_db_id = old_container.id
  293. db_volume_path = old_container.get_mount('/var/db')['Source']
  294. project.up(strategy=ConvergenceStrategy.never)
  295. new_containers = project.containers(stopped=True)
  296. self.assertEqual(len(new_containers), 2)
  297. self.assertEqual([c.is_running for c in new_containers], [True, True])
  298. db_container = [c for c in new_containers if 'db' in c.name][0]
  299. self.assertEqual(db_container.id, old_db_id)
  300. self.assertEqual(
  301. db_container.get_mount('/var/db')['Source'],
  302. db_volume_path)
  303. def test_project_up_without_all_services(self):
  304. console = self.create_service('console')
  305. db = self.create_service('db')
  306. project = Project('composetest', [console, db], self.client)
  307. project.start()
  308. self.assertEqual(len(project.containers()), 0)
  309. project.up()
  310. self.assertEqual(len(project.containers()), 2)
  311. self.assertEqual(len(db.containers()), 1)
  312. self.assertEqual(len(console.containers()), 1)
  313. def test_project_up_starts_links(self):
  314. console = self.create_service('console')
  315. db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
  316. web = self.create_service('web', links=[(db, 'db')])
  317. project = Project('composetest', [web, db, console], self.client)
  318. project.start()
  319. self.assertEqual(len(project.containers()), 0)
  320. project.up(['web'])
  321. self.assertEqual(len(project.containers()), 2)
  322. self.assertEqual(len(web.containers()), 1)
  323. self.assertEqual(len(db.containers()), 1)
  324. self.assertEqual(len(console.containers()), 0)
  325. def test_project_up_starts_depends(self):
  326. project = Project.from_config(
  327. name='composetest',
  328. config_data=build_service_dicts({
  329. 'console': {
  330. 'image': 'busybox:latest',
  331. 'command': ["top"],
  332. },
  333. 'data': {
  334. 'image': 'busybox:latest',
  335. 'command': ["top"]
  336. },
  337. 'db': {
  338. 'image': 'busybox:latest',
  339. 'command': ["top"],
  340. 'volumes_from': ['data'],
  341. },
  342. 'web': {
  343. 'image': 'busybox:latest',
  344. 'command': ["top"],
  345. 'links': ['db'],
  346. },
  347. }),
  348. client=self.client,
  349. )
  350. project.start()
  351. self.assertEqual(len(project.containers()), 0)
  352. project.up(['web'])
  353. self.assertEqual(len(project.containers()), 3)
  354. self.assertEqual(len(project.get_service('web').containers()), 1)
  355. self.assertEqual(len(project.get_service('db').containers()), 1)
  356. self.assertEqual(len(project.get_service('data').containers()), 1)
  357. self.assertEqual(len(project.get_service('console').containers()), 0)
  358. def test_project_up_with_no_deps(self):
  359. project = Project.from_config(
  360. name='composetest',
  361. config_data=build_service_dicts({
  362. 'console': {
  363. 'image': 'busybox:latest',
  364. 'command': ["top"],
  365. },
  366. 'data': {
  367. 'image': 'busybox:latest',
  368. 'command': ["top"]
  369. },
  370. 'db': {
  371. 'image': 'busybox:latest',
  372. 'command': ["top"],
  373. 'volumes_from': ['data'],
  374. },
  375. 'web': {
  376. 'image': 'busybox:latest',
  377. 'command': ["top"],
  378. 'links': ['db'],
  379. },
  380. }),
  381. client=self.client,
  382. )
  383. project.start()
  384. self.assertEqual(len(project.containers()), 0)
  385. project.up(['db'], start_deps=False)
  386. self.assertEqual(len(project.containers(stopped=True)), 2)
  387. self.assertEqual(len(project.get_service('web').containers()), 0)
  388. self.assertEqual(len(project.get_service('db').containers()), 1)
  389. self.assertEqual(len(project.get_service('data').containers()), 0)
  390. self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
  391. self.assertEqual(len(project.get_service('console').containers()), 0)
  392. def test_project_up_with_custom_network(self):
  393. self.require_api_version('1.21')
  394. client = docker_client(version='1.21')
  395. network_name = 'composetest-custom'
  396. client.create_network(network_name)
  397. self.addCleanup(client.remove_network, network_name)
  398. web = self.create_service('web', net=Net(network_name))
  399. project = Project('composetest', [web], client, use_networking=True)
  400. project.up()
  401. assert project.get_network() is None
  402. def test_unscale_after_restart(self):
  403. web = self.create_service('web')
  404. project = Project('composetest', [web], self.client)
  405. project.start()
  406. service = project.get_service('web')
  407. service.scale(1)
  408. self.assertEqual(len(service.containers()), 1)
  409. service.scale(3)
  410. self.assertEqual(len(service.containers()), 3)
  411. project.up()
  412. service = project.get_service('web')
  413. self.assertEqual(len(service.containers()), 3)
  414. service.scale(1)
  415. self.assertEqual(len(service.containers()), 1)
  416. project.up()
  417. service = project.get_service('web')
  418. self.assertEqual(len(service.containers()), 1)
  419. # does scale=0 ,makes any sense? after recreating at least 1 container is running
  420. service.scale(0)
  421. project.up()
  422. service = project.get_service('web')
  423. self.assertEqual(len(service.containers()), 1)
  424. def test_project_up_volumes(self):
  425. vol_name = '{0:x}'.format(random.getrandbits(32))
  426. full_vol_name = 'composetest_{0}'.format(vol_name)
  427. config_data = config.Config(
  428. version=2, services=[{
  429. 'name': 'web',
  430. 'image': 'busybox:latest',
  431. 'command': 'top'
  432. }], volumes={vol_name: {'driver': 'local'}}
  433. )
  434. project = Project.from_config(
  435. name='composetest',
  436. config_data=config_data, client=self.client
  437. )
  438. project.up()
  439. self.assertEqual(len(project.containers()), 1)
  440. volume_data = self.client.inspect_volume(full_vol_name)
  441. self.assertEqual(volume_data['Name'], full_vol_name)
  442. self.assertEqual(volume_data['Driver'], 'local')
  443. def test_initialize_volumes(self):
  444. vol_name = '{0:x}'.format(random.getrandbits(32))
  445. full_vol_name = 'composetest_{0}'.format(vol_name)
  446. config_data = config.Config(
  447. version=2, services=[{
  448. 'name': 'web',
  449. 'image': 'busybox:latest',
  450. 'command': 'top'
  451. }], volumes={vol_name: {}}
  452. )
  453. project = Project.from_config(
  454. name='composetest',
  455. config_data=config_data, client=self.client
  456. )
  457. project.initialize_volumes()
  458. volume_data = self.client.inspect_volume(full_vol_name)
  459. self.assertEqual(volume_data['Name'], full_vol_name)
  460. self.assertEqual(volume_data['Driver'], 'local')
  461. def test_project_up_implicit_volume_driver(self):
  462. vol_name = '{0:x}'.format(random.getrandbits(32))
  463. full_vol_name = 'composetest_{0}'.format(vol_name)
  464. config_data = config.Config(
  465. version=2, services=[{
  466. 'name': 'web',
  467. 'image': 'busybox:latest',
  468. 'command': 'top'
  469. }], volumes={vol_name: {}}
  470. )
  471. project = Project.from_config(
  472. name='composetest',
  473. config_data=config_data, client=self.client
  474. )
  475. project.up()
  476. volume_data = self.client.inspect_volume(full_vol_name)
  477. self.assertEqual(volume_data['Name'], full_vol_name)
  478. self.assertEqual(volume_data['Driver'], 'local')
  479. def test_project_up_invalid_volume_driver(self):
  480. vol_name = '{0:x}'.format(random.getrandbits(32))
  481. config_data = config.Config(
  482. version=2, services=[{
  483. 'name': 'web',
  484. 'image': 'busybox:latest',
  485. 'command': 'top'
  486. }], volumes={vol_name: {'driver': 'foobar'}}
  487. )
  488. project = Project.from_config(
  489. name='composetest',
  490. config_data=config_data, client=self.client
  491. )
  492. with self.assertRaises(config.ConfigurationError):
  493. project.initialize_volumes()
  494. def test_project_up_updated_driver(self):
  495. vol_name = '{0:x}'.format(random.getrandbits(32))
  496. full_vol_name = 'composetest_{0}'.format(vol_name)
  497. config_data = config.Config(
  498. version=2, services=[{
  499. 'name': 'web',
  500. 'image': 'busybox:latest',
  501. 'command': 'top'
  502. }], volumes={vol_name: {'driver': 'local'}}
  503. )
  504. project = Project.from_config(
  505. name='composetest',
  506. config_data=config_data, client=self.client
  507. )
  508. project.initialize_volumes()
  509. volume_data = self.client.inspect_volume(full_vol_name)
  510. self.assertEqual(volume_data['Name'], full_vol_name)
  511. self.assertEqual(volume_data['Driver'], 'local')
  512. config_data = config_data._replace(
  513. volumes={vol_name: {'driver': 'smb'}}
  514. )
  515. project = Project.from_config(
  516. name='composetest',
  517. config_data=config_data, client=self.client
  518. )
  519. with self.assertRaises(config.ConfigurationError) as e:
  520. project.initialize_volumes()
  521. assert 'Configuration for volume {0} specifies driver smb'.format(
  522. vol_name
  523. ) in str(e.exception)