project_test.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import datetime
  4. import docker
  5. from docker.errors import NotFound
  6. from .. import mock
  7. from .. import unittest
  8. from compose.config.config import Config
  9. from compose.config.types import VolumeFromSpec
  10. from compose.const import LABEL_SERVICE
  11. from compose.container import Container
  12. from compose.project import Project
  13. from compose.service import ImageType
  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_config(self):
  19. config = Config(
  20. version=None,
  21. services=[
  22. {
  23. 'name': 'web',
  24. 'image': 'busybox:latest',
  25. },
  26. {
  27. 'name': 'db',
  28. 'image': 'busybox:latest',
  29. },
  30. ],
  31. networks=None,
  32. volumes=None,
  33. )
  34. project = Project.from_config(
  35. name='composetest',
  36. config_data=config,
  37. client=None,
  38. )
  39. self.assertEqual(len(project.services), 2)
  40. self.assertEqual(project.get_service('web').name, 'web')
  41. self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
  42. self.assertEqual(project.get_service('db').name, 'db')
  43. self.assertEqual(project.get_service('db').options['image'], 'busybox:latest')
  44. self.assertFalse(project.networks.use_networking)
  45. def test_from_config_v2(self):
  46. config = Config(
  47. version=2,
  48. services=[
  49. {
  50. 'name': 'web',
  51. 'image': 'busybox:latest',
  52. },
  53. {
  54. 'name': 'db',
  55. 'image': 'busybox:latest',
  56. },
  57. ],
  58. networks=None,
  59. volumes=None,
  60. )
  61. project = Project.from_config('composetest', config, None)
  62. self.assertEqual(len(project.services), 2)
  63. self.assertTrue(project.networks.use_networking)
  64. def test_get_service(self):
  65. web = Service(
  66. project='composetest',
  67. name='web',
  68. client=None,
  69. image="busybox:latest",
  70. )
  71. project = Project('test', [web], None)
  72. self.assertEqual(project.get_service('web'), web)
  73. def test_get_services_returns_all_services_without_args(self):
  74. web = Service(
  75. project='composetest',
  76. name='web',
  77. image='foo',
  78. )
  79. console = Service(
  80. project='composetest',
  81. name='console',
  82. image='foo',
  83. )
  84. project = Project('test', [web, console], None)
  85. self.assertEqual(project.get_services(), [web, console])
  86. def test_get_services_returns_listed_services_with_args(self):
  87. web = Service(
  88. project='composetest',
  89. name='web',
  90. image='foo',
  91. )
  92. console = Service(
  93. project='composetest',
  94. name='console',
  95. image='foo',
  96. )
  97. project = Project('test', [web, console], None)
  98. self.assertEqual(project.get_services(['console']), [console])
  99. def test_get_services_with_include_links(self):
  100. db = Service(
  101. project='composetest',
  102. name='db',
  103. image='foo',
  104. )
  105. web = Service(
  106. project='composetest',
  107. name='web',
  108. image='foo',
  109. links=[(db, 'database')]
  110. )
  111. cache = Service(
  112. project='composetest',
  113. name='cache',
  114. image='foo'
  115. )
  116. console = Service(
  117. project='composetest',
  118. name='console',
  119. image='foo',
  120. links=[(web, 'web')]
  121. )
  122. project = Project('test', [web, db, cache, console], None)
  123. self.assertEqual(
  124. project.get_services(['console'], include_deps=True),
  125. [db, web, console]
  126. )
  127. def test_get_services_removes_duplicates_following_links(self):
  128. db = Service(
  129. project='composetest',
  130. name='db',
  131. image='foo',
  132. )
  133. web = Service(
  134. project='composetest',
  135. name='web',
  136. image='foo',
  137. links=[(db, 'database')]
  138. )
  139. project = Project('test', [web, db], None)
  140. self.assertEqual(
  141. project.get_services(['web', 'db'], include_deps=True),
  142. [db, web]
  143. )
  144. def test_use_volumes_from_container(self):
  145. container_id = 'aabbccddee'
  146. container_dict = dict(Name='aaa', Id=container_id)
  147. self.mock_client.inspect_container.return_value = container_dict
  148. project = Project.from_config(
  149. name='test',
  150. client=self.mock_client,
  151. config_data=Config(
  152. version=None,
  153. services=[{
  154. 'name': 'test',
  155. 'image': 'busybox:latest',
  156. 'volumes_from': [VolumeFromSpec('aaa', 'rw', 'container')]
  157. }],
  158. networks=None,
  159. volumes=None,
  160. ),
  161. )
  162. assert project.get_service('test')._get_volumes_from() == [container_id + ":rw"]
  163. def test_use_volumes_from_service_no_container(self):
  164. container_name = 'test_vol_1'
  165. self.mock_client.containers.return_value = [
  166. {
  167. "Name": container_name,
  168. "Names": [container_name],
  169. "Id": container_name,
  170. "Image": 'busybox:latest'
  171. }
  172. ]
  173. project = Project.from_config(
  174. name='test',
  175. client=self.mock_client,
  176. config_data=Config(
  177. version=None,
  178. services=[
  179. {
  180. 'name': 'vol',
  181. 'image': 'busybox:latest'
  182. },
  183. {
  184. 'name': 'test',
  185. 'image': 'busybox:latest',
  186. 'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
  187. }
  188. ],
  189. networks=None,
  190. volumes=None,
  191. ),
  192. )
  193. assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
  194. def test_use_volumes_from_service_container(self):
  195. container_ids = ['aabbccddee', '12345']
  196. project = Project.from_config(
  197. name='test',
  198. client=None,
  199. config_data=Config(
  200. version=None,
  201. services=[
  202. {
  203. 'name': 'vol',
  204. 'image': 'busybox:latest'
  205. },
  206. {
  207. 'name': 'test',
  208. 'image': 'busybox:latest',
  209. 'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
  210. }
  211. ],
  212. networks=None,
  213. volumes=None,
  214. ),
  215. )
  216. with mock.patch.object(Service, 'containers') as mock_return:
  217. mock_return.return_value = [
  218. mock.Mock(id=container_id, spec=Container)
  219. for container_id in container_ids]
  220. assert (
  221. project.get_service('test')._get_volumes_from() ==
  222. [container_ids[0] + ':rw']
  223. )
  224. def test_events(self):
  225. services = [Service(name='web'), Service(name='db')]
  226. project = Project('test', services, self.mock_client)
  227. self.mock_client.events.return_value = iter([
  228. {
  229. 'status': 'create',
  230. 'from': 'example/image',
  231. 'id': 'abcde',
  232. 'time': 1420092061,
  233. 'timeNano': 14200920610000002000,
  234. },
  235. {
  236. 'status': 'attach',
  237. 'from': 'example/image',
  238. 'id': 'abcde',
  239. 'time': 1420092061,
  240. 'timeNano': 14200920610000003000,
  241. },
  242. {
  243. 'status': 'create',
  244. 'from': 'example/other',
  245. 'id': 'bdbdbd',
  246. 'time': 1420092061,
  247. 'timeNano': 14200920610000005000,
  248. },
  249. {
  250. 'status': 'create',
  251. 'from': 'example/db',
  252. 'id': 'ababa',
  253. 'time': 1420092061,
  254. 'timeNano': 14200920610000004000,
  255. },
  256. ])
  257. def dt_with_microseconds(dt, us):
  258. return datetime.datetime.fromtimestamp(dt).replace(microsecond=us)
  259. def get_container(cid):
  260. if cid == 'abcde':
  261. name = 'web'
  262. labels = {LABEL_SERVICE: name}
  263. elif cid == 'ababa':
  264. name = 'db'
  265. labels = {LABEL_SERVICE: name}
  266. else:
  267. labels = {}
  268. name = ''
  269. return {
  270. 'Id': cid,
  271. 'Config': {'Labels': labels},
  272. 'Name': '/project_%s_1' % name,
  273. }
  274. self.mock_client.inspect_container.side_effect = get_container
  275. events = project.events()
  276. events_list = list(events)
  277. # Assert the return value is a generator
  278. assert not list(events)
  279. assert events_list == [
  280. {
  281. 'type': 'container',
  282. 'service': 'web',
  283. 'action': 'create',
  284. 'id': 'abcde',
  285. 'attributes': {
  286. 'name': 'project_web_1',
  287. 'image': 'example/image',
  288. },
  289. 'time': dt_with_microseconds(1420092061, 2),
  290. },
  291. {
  292. 'type': 'container',
  293. 'service': 'web',
  294. 'action': 'attach',
  295. 'id': 'abcde',
  296. 'attributes': {
  297. 'name': 'project_web_1',
  298. 'image': 'example/image',
  299. },
  300. 'time': dt_with_microseconds(1420092061, 3),
  301. },
  302. {
  303. 'type': 'container',
  304. 'service': 'db',
  305. 'action': 'create',
  306. 'id': 'ababa',
  307. 'attributes': {
  308. 'name': 'project_db_1',
  309. 'image': 'example/db',
  310. },
  311. 'time': dt_with_microseconds(1420092061, 4),
  312. },
  313. ]
  314. def test_net_unset(self):
  315. project = Project.from_config(
  316. name='test',
  317. client=self.mock_client,
  318. config_data=Config(
  319. version=None,
  320. services=[
  321. {
  322. 'name': 'test',
  323. 'image': 'busybox:latest',
  324. }
  325. ],
  326. networks=None,
  327. volumes=None,
  328. ),
  329. )
  330. service = project.get_service('test')
  331. self.assertEqual(service.network_mode.id, None)
  332. self.assertNotIn('NetworkMode', service._get_container_host_config({}))
  333. def test_use_net_from_container(self):
  334. container_id = 'aabbccddee'
  335. container_dict = dict(Name='aaa', Id=container_id)
  336. self.mock_client.inspect_container.return_value = container_dict
  337. project = Project.from_config(
  338. name='test',
  339. client=self.mock_client,
  340. config_data=Config(
  341. version=None,
  342. services=[
  343. {
  344. 'name': 'test',
  345. 'image': 'busybox:latest',
  346. 'network_mode': 'container:aaa'
  347. },
  348. ],
  349. networks=None,
  350. volumes=None,
  351. ),
  352. )
  353. service = project.get_service('test')
  354. self.assertEqual(service.network_mode.mode, 'container:' + container_id)
  355. def test_use_net_from_service(self):
  356. container_name = 'test_aaa_1'
  357. self.mock_client.containers.return_value = [
  358. {
  359. "Name": container_name,
  360. "Names": [container_name],
  361. "Id": container_name,
  362. "Image": 'busybox:latest'
  363. }
  364. ]
  365. project = Project.from_config(
  366. name='test',
  367. client=self.mock_client,
  368. config_data=Config(
  369. version=None,
  370. services=[
  371. {
  372. 'name': 'aaa',
  373. 'image': 'busybox:latest'
  374. },
  375. {
  376. 'name': 'test',
  377. 'image': 'busybox:latest',
  378. 'network_mode': 'service:aaa'
  379. },
  380. ],
  381. networks=None,
  382. volumes=None,
  383. ),
  384. )
  385. service = project.get_service('test')
  386. self.assertEqual(service.network_mode.mode, 'container:' + container_name)
  387. def test_uses_default_network_true(self):
  388. project = Project.from_config(
  389. name='test',
  390. client=self.mock_client,
  391. config_data=Config(
  392. version=2,
  393. services=[
  394. {
  395. 'name': 'foo',
  396. 'image': 'busybox:latest'
  397. },
  398. ],
  399. networks=None,
  400. volumes=None,
  401. ),
  402. )
  403. assert 'default' in project.networks.networks
  404. def test_uses_default_network_false(self):
  405. project = Project.from_config(
  406. name='test',
  407. client=self.mock_client,
  408. config_data=Config(
  409. version=2,
  410. services=[
  411. {
  412. 'name': 'foo',
  413. 'image': 'busybox:latest',
  414. 'networks': {'custom': None}
  415. },
  416. ],
  417. networks={'custom': {}},
  418. volumes=None,
  419. ),
  420. )
  421. assert 'default' not in project.networks.networks
  422. def test_container_without_name(self):
  423. self.mock_client.containers.return_value = [
  424. {'Image': 'busybox:latest', 'Id': '1', 'Name': '1'},
  425. {'Image': 'busybox:latest', 'Id': '2', 'Name': None},
  426. {'Image': 'busybox:latest', 'Id': '3'},
  427. ]
  428. self.mock_client.inspect_container.return_value = {
  429. 'Id': '1',
  430. 'Config': {
  431. 'Labels': {
  432. LABEL_SERVICE: 'web',
  433. },
  434. },
  435. }
  436. project = Project.from_config(
  437. name='test',
  438. client=self.mock_client,
  439. config_data=Config(
  440. version=None,
  441. services=[{
  442. 'name': 'web',
  443. 'image': 'busybox:latest',
  444. }],
  445. networks=None,
  446. volumes=None,
  447. ),
  448. )
  449. self.assertEqual([c.id for c in project.containers()], ['1'])
  450. def test_down_with_no_resources(self):
  451. project = Project.from_config(
  452. name='test',
  453. client=self.mock_client,
  454. config_data=Config(
  455. version='2',
  456. services=[{
  457. 'name': 'web',
  458. 'image': 'busybox:latest',
  459. }],
  460. networks={'default': {}},
  461. volumes={'data': {}},
  462. ),
  463. )
  464. self.mock_client.remove_network.side_effect = NotFound(None, None, 'oops')
  465. self.mock_client.remove_volume.side_effect = NotFound(None, None, 'oops')
  466. project.down(ImageType.all, True)
  467. self.mock_client.remove_image.assert_called_once_with("busybox:latest")