project_test.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import datetime
  4. import docker
  5. from .. import mock
  6. from .. import unittest
  7. from compose.config.config import Config
  8. from compose.config.types import VolumeFromSpec
  9. from compose.const import LABEL_SERVICE
  10. from compose.container import Container
  11. from compose.project import Project
  12. from compose.service import ContainerNet
  13. from compose.service import Net
  14. from compose.service import Service
  15. class ProjectTest(unittest.TestCase):
  16. def setUp(self):
  17. self.mock_client = mock.create_autospec(docker.Client)
  18. def test_from_dict(self):
  19. project = Project.from_config('composetest', Config(None, [
  20. {
  21. 'name': 'web',
  22. 'image': 'busybox:latest'
  23. },
  24. {
  25. 'name': 'db',
  26. 'image': 'busybox:latest'
  27. },
  28. ], None), None)
  29. self.assertEqual(len(project.services), 2)
  30. self.assertEqual(project.get_service('web').name, 'web')
  31. self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
  32. self.assertEqual(project.get_service('db').name, 'db')
  33. self.assertEqual(project.get_service('db').options['image'], 'busybox:latest')
  34. def test_from_config(self):
  35. config = Config(None, [
  36. {
  37. 'name': 'web',
  38. 'image': 'busybox:latest',
  39. },
  40. {
  41. 'name': 'db',
  42. 'image': 'busybox:latest',
  43. },
  44. ], None)
  45. project = Project.from_config('composetest', config, None)
  46. self.assertEqual(len(project.services), 2)
  47. self.assertEqual(project.get_service('web').name, 'web')
  48. self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
  49. self.assertEqual(project.get_service('db').name, 'db')
  50. self.assertEqual(project.get_service('db').options['image'], 'busybox:latest')
  51. self.assertFalse(project.use_networking)
  52. def test_from_config_v2(self):
  53. config = Config(2, [
  54. {
  55. 'name': 'web',
  56. 'image': 'busybox:latest',
  57. },
  58. {
  59. 'name': 'db',
  60. 'image': 'busybox:latest',
  61. },
  62. ], None)
  63. project = Project.from_config('composetest', config, None)
  64. self.assertEqual(len(project.services), 2)
  65. self.assertTrue(project.use_networking)
  66. def test_get_service(self):
  67. web = Service(
  68. project='composetest',
  69. name='web',
  70. client=None,
  71. image="busybox:latest",
  72. )
  73. project = Project('test', [web], None)
  74. self.assertEqual(project.get_service('web'), web)
  75. def test_get_services_returns_all_services_without_args(self):
  76. web = Service(
  77. project='composetest',
  78. name='web',
  79. image='foo',
  80. )
  81. console = Service(
  82. project='composetest',
  83. name='console',
  84. image='foo',
  85. )
  86. project = Project('test', [web, console], None)
  87. self.assertEqual(project.get_services(), [web, console])
  88. def test_get_services_returns_listed_services_with_args(self):
  89. web = Service(
  90. project='composetest',
  91. name='web',
  92. image='foo',
  93. )
  94. console = Service(
  95. project='composetest',
  96. name='console',
  97. image='foo',
  98. )
  99. project = Project('test', [web, console], None)
  100. self.assertEqual(project.get_services(['console']), [console])
  101. def test_get_services_with_include_links(self):
  102. db = Service(
  103. project='composetest',
  104. name='db',
  105. image='foo',
  106. )
  107. web = Service(
  108. project='composetest',
  109. name='web',
  110. image='foo',
  111. links=[(db, 'database')]
  112. )
  113. cache = Service(
  114. project='composetest',
  115. name='cache',
  116. image='foo'
  117. )
  118. console = Service(
  119. project='composetest',
  120. name='console',
  121. image='foo',
  122. links=[(web, 'web')]
  123. )
  124. project = Project('test', [web, db, cache, console], None)
  125. self.assertEqual(
  126. project.get_services(['console'], include_deps=True),
  127. [db, web, console]
  128. )
  129. def test_get_services_removes_duplicates_following_links(self):
  130. db = Service(
  131. project='composetest',
  132. name='db',
  133. image='foo',
  134. )
  135. web = Service(
  136. project='composetest',
  137. name='web',
  138. image='foo',
  139. links=[(db, 'database')]
  140. )
  141. project = Project('test', [web, db], None)
  142. self.assertEqual(
  143. project.get_services(['web', 'db'], include_deps=True),
  144. [db, web]
  145. )
  146. def test_use_volumes_from_container(self):
  147. container_id = 'aabbccddee'
  148. container_dict = dict(Name='aaa', Id=container_id)
  149. self.mock_client.inspect_container.return_value = container_dict
  150. project = Project.from_config('test', Config(None, [
  151. {
  152. 'name': 'test',
  153. 'image': 'busybox:latest',
  154. 'volumes_from': [VolumeFromSpec('aaa', 'rw')]
  155. }
  156. ], None), self.mock_client)
  157. self.assertEqual(project.get_service('test')._get_volumes_from(), [container_id + ":rw"])
  158. def test_use_volumes_from_service_no_container(self):
  159. container_name = 'test_vol_1'
  160. self.mock_client.containers.return_value = [
  161. {
  162. "Name": container_name,
  163. "Names": [container_name],
  164. "Id": container_name,
  165. "Image": 'busybox:latest'
  166. }
  167. ]
  168. project = Project.from_config('test', Config(None, [
  169. {
  170. 'name': 'vol',
  171. 'image': 'busybox:latest'
  172. },
  173. {
  174. 'name': 'test',
  175. 'image': 'busybox:latest',
  176. 'volumes_from': [VolumeFromSpec('vol', 'rw')]
  177. }
  178. ], None), self.mock_client)
  179. self.assertEqual(project.get_service('test')._get_volumes_from(), [container_name + ":rw"])
  180. def test_use_volumes_from_service_container(self):
  181. container_ids = ['aabbccddee', '12345']
  182. project = Project.from_config('test', Config(None, [
  183. {
  184. 'name': 'vol',
  185. 'image': 'busybox:latest'
  186. },
  187. {
  188. 'name': 'test',
  189. 'image': 'busybox:latest',
  190. 'volumes_from': [VolumeFromSpec('vol', 'rw')]
  191. }
  192. ], None), None)
  193. with mock.patch.object(Service, 'containers') as mock_return:
  194. mock_return.return_value = [
  195. mock.Mock(id=container_id, spec=Container)
  196. for container_id in container_ids]
  197. self.assertEqual(
  198. project.get_service('test')._get_volumes_from(),
  199. [container_ids[0] + ':rw'])
  200. def test_events(self):
  201. services = [Service(name='web'), Service(name='db')]
  202. project = Project('test', services, self.mock_client)
  203. self.mock_client.events.return_value = iter([
  204. {
  205. 'status': 'create',
  206. 'from': 'example/image',
  207. 'id': 'abcde',
  208. 'time': 1420092061,
  209. 'timeNano': 14200920610000002000,
  210. },
  211. {
  212. 'status': 'attach',
  213. 'from': 'example/image',
  214. 'id': 'abcde',
  215. 'time': 1420092061,
  216. 'timeNano': 14200920610000003000,
  217. },
  218. {
  219. 'status': 'create',
  220. 'from': 'example/other',
  221. 'id': 'bdbdbd',
  222. 'time': 1420092061,
  223. 'timeNano': 14200920610000005000,
  224. },
  225. {
  226. 'status': 'create',
  227. 'from': 'example/db',
  228. 'id': 'ababa',
  229. 'time': 1420092061,
  230. 'timeNano': 14200920610000004000,
  231. },
  232. ])
  233. def dt_with_microseconds(dt, us):
  234. return datetime.datetime.fromtimestamp(dt).replace(microsecond=us)
  235. def get_container(cid):
  236. if cid == 'abcde':
  237. name = 'web'
  238. labels = {LABEL_SERVICE: name}
  239. elif cid == 'ababa':
  240. name = 'db'
  241. labels = {LABEL_SERVICE: name}
  242. else:
  243. labels = {}
  244. name = ''
  245. return {
  246. 'Id': cid,
  247. 'Config': {'Labels': labels},
  248. 'Name': '/project_%s_1' % name,
  249. }
  250. self.mock_client.inspect_container.side_effect = get_container
  251. events = project.events()
  252. events_list = list(events)
  253. # Assert the return value is a generator
  254. assert not list(events)
  255. assert events_list == [
  256. {
  257. 'type': 'container',
  258. 'service': 'web',
  259. 'action': 'create',
  260. 'id': 'abcde',
  261. 'attributes': {
  262. 'name': 'project_web_1',
  263. 'image': 'example/image',
  264. },
  265. 'time': dt_with_microseconds(1420092061, 2),
  266. },
  267. {
  268. 'type': 'container',
  269. 'service': 'web',
  270. 'action': 'attach',
  271. 'id': 'abcde',
  272. 'attributes': {
  273. 'name': 'project_web_1',
  274. 'image': 'example/image',
  275. },
  276. 'time': dt_with_microseconds(1420092061, 3),
  277. },
  278. {
  279. 'type': 'container',
  280. 'service': 'db',
  281. 'action': 'create',
  282. 'id': 'ababa',
  283. 'attributes': {
  284. 'name': 'project_db_1',
  285. 'image': 'example/db',
  286. },
  287. 'time': dt_with_microseconds(1420092061, 4),
  288. },
  289. ]
  290. def test_net_unset(self):
  291. project = Project.from_config('test', Config(None, [
  292. {
  293. 'name': 'test',
  294. 'image': 'busybox:latest',
  295. }
  296. ], None), self.mock_client)
  297. service = project.get_service('test')
  298. self.assertEqual(service.net.id, None)
  299. self.assertNotIn('NetworkMode', service._get_container_host_config({}))
  300. def test_use_net_from_container(self):
  301. container_id = 'aabbccddee'
  302. container_dict = dict(Name='aaa', Id=container_id)
  303. self.mock_client.inspect_container.return_value = container_dict
  304. project = Project.from_config('test', Config(None, [
  305. {
  306. 'name': 'test',
  307. 'image': 'busybox:latest',
  308. 'net': 'container:aaa'
  309. }
  310. ], None), self.mock_client)
  311. service = project.get_service('test')
  312. self.assertEqual(service.net.mode, 'container:' + container_id)
  313. def test_use_net_from_service(self):
  314. container_name = 'test_aaa_1'
  315. self.mock_client.containers.return_value = [
  316. {
  317. "Name": container_name,
  318. "Names": [container_name],
  319. "Id": container_name,
  320. "Image": 'busybox:latest'
  321. }
  322. ]
  323. project = Project.from_config('test', Config(None, [
  324. {
  325. 'name': 'aaa',
  326. 'image': 'busybox:latest'
  327. },
  328. {
  329. 'name': 'test',
  330. 'image': 'busybox:latest',
  331. 'net': 'container:aaa'
  332. }
  333. ], None), self.mock_client)
  334. service = project.get_service('test')
  335. self.assertEqual(service.net.mode, 'container:' + container_name)
  336. def test_uses_default_network_true(self):
  337. web = Service('web', project='test', image="alpine", net=Net('test_default'))
  338. db = Service('web', project='test', image="alpine", net=Net('other'))
  339. project = Project('test', [web, db], None)
  340. assert project.uses_default_network()
  341. def test_uses_default_network_custom_name(self):
  342. web = Service('web', project='test', image="alpine", net=Net('other'))
  343. project = Project('test', [web], None)
  344. assert not project.uses_default_network()
  345. def test_uses_default_network_host(self):
  346. web = Service('web', project='test', image="alpine", net=Net('host'))
  347. project = Project('test', [web], None)
  348. assert not project.uses_default_network()
  349. def test_uses_default_network_container(self):
  350. container = mock.Mock(id='test')
  351. web = Service(
  352. 'web',
  353. project='test',
  354. image="alpine",
  355. net=ContainerNet(container))
  356. project = Project('test', [web], None)
  357. assert not project.uses_default_network()
  358. def test_container_without_name(self):
  359. self.mock_client.containers.return_value = [
  360. {'Image': 'busybox:latest', 'Id': '1', 'Name': '1'},
  361. {'Image': 'busybox:latest', 'Id': '2', 'Name': None},
  362. {'Image': 'busybox:latest', 'Id': '3'},
  363. ]
  364. self.mock_client.inspect_container.return_value = {
  365. 'Id': '1',
  366. 'Config': {
  367. 'Labels': {
  368. LABEL_SERVICE: 'web',
  369. },
  370. },
  371. }
  372. project = Project.from_config(
  373. 'test',
  374. Config(None, [{
  375. 'name': 'web',
  376. 'image': 'busybox:latest',
  377. }], None),
  378. self.mock_client,
  379. )
  380. self.assertEqual([c.id for c in project.containers()], ['1'])