project_test.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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. 'status': 'destroy',
  258. 'from': 'example/db',
  259. 'id': 'eeeee',
  260. 'time': 1420092061,
  261. 'timeNano': 14200920610000004000,
  262. },
  263. ])
  264. def dt_with_microseconds(dt, us):
  265. return datetime.datetime.fromtimestamp(dt).replace(microsecond=us)
  266. def get_container(cid):
  267. if cid == 'eeeee':
  268. raise NotFound(None, None, "oops")
  269. if cid == 'abcde':
  270. name = 'web'
  271. labels = {LABEL_SERVICE: name}
  272. elif cid == 'ababa':
  273. name = 'db'
  274. labels = {LABEL_SERVICE: name}
  275. else:
  276. labels = {}
  277. name = ''
  278. return {
  279. 'Id': cid,
  280. 'Config': {'Labels': labels},
  281. 'Name': '/project_%s_1' % name,
  282. }
  283. self.mock_client.inspect_container.side_effect = get_container
  284. events = project.events()
  285. events_list = list(events)
  286. # Assert the return value is a generator
  287. assert not list(events)
  288. assert events_list == [
  289. {
  290. 'type': 'container',
  291. 'service': 'web',
  292. 'action': 'create',
  293. 'id': 'abcde',
  294. 'attributes': {
  295. 'name': 'project_web_1',
  296. 'image': 'example/image',
  297. },
  298. 'time': dt_with_microseconds(1420092061, 2),
  299. 'container': Container(None, {'Id': 'abcde'}),
  300. },
  301. {
  302. 'type': 'container',
  303. 'service': 'web',
  304. 'action': 'attach',
  305. 'id': 'abcde',
  306. 'attributes': {
  307. 'name': 'project_web_1',
  308. 'image': 'example/image',
  309. },
  310. 'time': dt_with_microseconds(1420092061, 3),
  311. 'container': Container(None, {'Id': 'abcde'}),
  312. },
  313. {
  314. 'type': 'container',
  315. 'service': 'db',
  316. 'action': 'create',
  317. 'id': 'ababa',
  318. 'attributes': {
  319. 'name': 'project_db_1',
  320. 'image': 'example/db',
  321. },
  322. 'time': dt_with_microseconds(1420092061, 4),
  323. 'container': Container(None, {'Id': 'ababa'}),
  324. },
  325. ]
  326. def test_net_unset(self):
  327. project = Project.from_config(
  328. name='test',
  329. client=self.mock_client,
  330. config_data=Config(
  331. version=None,
  332. services=[
  333. {
  334. 'name': 'test',
  335. 'image': 'busybox:latest',
  336. }
  337. ],
  338. networks=None,
  339. volumes=None,
  340. ),
  341. )
  342. service = project.get_service('test')
  343. self.assertEqual(service.network_mode.id, None)
  344. self.assertNotIn('NetworkMode', service._get_container_host_config({}))
  345. def test_use_net_from_container(self):
  346. container_id = 'aabbccddee'
  347. container_dict = dict(Name='aaa', Id=container_id)
  348. self.mock_client.inspect_container.return_value = container_dict
  349. project = Project.from_config(
  350. name='test',
  351. client=self.mock_client,
  352. config_data=Config(
  353. version=None,
  354. services=[
  355. {
  356. 'name': 'test',
  357. 'image': 'busybox:latest',
  358. 'network_mode': 'container:aaa'
  359. },
  360. ],
  361. networks=None,
  362. volumes=None,
  363. ),
  364. )
  365. service = project.get_service('test')
  366. self.assertEqual(service.network_mode.mode, 'container:' + container_id)
  367. def test_use_net_from_service(self):
  368. container_name = 'test_aaa_1'
  369. self.mock_client.containers.return_value = [
  370. {
  371. "Name": container_name,
  372. "Names": [container_name],
  373. "Id": container_name,
  374. "Image": 'busybox:latest'
  375. }
  376. ]
  377. project = Project.from_config(
  378. name='test',
  379. client=self.mock_client,
  380. config_data=Config(
  381. version=None,
  382. services=[
  383. {
  384. 'name': 'aaa',
  385. 'image': 'busybox:latest'
  386. },
  387. {
  388. 'name': 'test',
  389. 'image': 'busybox:latest',
  390. 'network_mode': 'service:aaa'
  391. },
  392. ],
  393. networks=None,
  394. volumes=None,
  395. ),
  396. )
  397. service = project.get_service('test')
  398. self.assertEqual(service.network_mode.mode, 'container:' + container_name)
  399. def test_uses_default_network_true(self):
  400. project = Project.from_config(
  401. name='test',
  402. client=self.mock_client,
  403. config_data=Config(
  404. version=2,
  405. services=[
  406. {
  407. 'name': 'foo',
  408. 'image': 'busybox:latest'
  409. },
  410. ],
  411. networks=None,
  412. volumes=None,
  413. ),
  414. )
  415. assert 'default' in project.networks.networks
  416. def test_uses_default_network_false(self):
  417. project = Project.from_config(
  418. name='test',
  419. client=self.mock_client,
  420. config_data=Config(
  421. version=2,
  422. services=[
  423. {
  424. 'name': 'foo',
  425. 'image': 'busybox:latest',
  426. 'networks': {'custom': None}
  427. },
  428. ],
  429. networks={'custom': {}},
  430. volumes=None,
  431. ),
  432. )
  433. assert 'default' not in project.networks.networks
  434. def test_container_without_name(self):
  435. self.mock_client.containers.return_value = [
  436. {'Image': 'busybox:latest', 'Id': '1', 'Name': '1'},
  437. {'Image': 'busybox:latest', 'Id': '2', 'Name': None},
  438. {'Image': 'busybox:latest', 'Id': '3'},
  439. ]
  440. self.mock_client.inspect_container.return_value = {
  441. 'Id': '1',
  442. 'Config': {
  443. 'Labels': {
  444. LABEL_SERVICE: 'web',
  445. },
  446. },
  447. }
  448. project = Project.from_config(
  449. name='test',
  450. client=self.mock_client,
  451. config_data=Config(
  452. version=None,
  453. services=[{
  454. 'name': 'web',
  455. 'image': 'busybox:latest',
  456. }],
  457. networks=None,
  458. volumes=None,
  459. ),
  460. )
  461. self.assertEqual([c.id for c in project.containers()], ['1'])
  462. def test_down_with_no_resources(self):
  463. project = Project.from_config(
  464. name='test',
  465. client=self.mock_client,
  466. config_data=Config(
  467. version='2',
  468. services=[{
  469. 'name': 'web',
  470. 'image': 'busybox:latest',
  471. }],
  472. networks={'default': {}},
  473. volumes={'data': {}},
  474. ),
  475. )
  476. self.mock_client.remove_network.side_effect = NotFound(None, None, 'oops')
  477. self.mock_client.remove_volume.side_effect = NotFound(None, None, 'oops')
  478. project.down(ImageType.all, True)
  479. self.mock_client.remove_image.assert_called_once_with("busybox:latest")