service_test.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. from __future__ import unicode_literals
  2. from __future__ import absolute_import
  3. import os
  4. from .. import unittest
  5. import mock
  6. import docker
  7. from requests import Response
  8. from compose import Service
  9. from compose.container import Container
  10. from compose.service import (
  11. APIError,
  12. ConfigError,
  13. build_port_bindings,
  14. build_volume_binding,
  15. get_container_name,
  16. parse_repository_tag,
  17. parse_volume_spec,
  18. split_port,
  19. )
  20. class ServiceTest(unittest.TestCase):
  21. def setUp(self):
  22. self.mock_client = mock.create_autospec(docker.Client)
  23. def test_name_validations(self):
  24. self.assertRaises(ConfigError, lambda: Service(name=''))
  25. self.assertRaises(ConfigError, lambda: Service(name=' '))
  26. self.assertRaises(ConfigError, lambda: Service(name='/'))
  27. self.assertRaises(ConfigError, lambda: Service(name='!'))
  28. self.assertRaises(ConfigError, lambda: Service(name='\xe2'))
  29. self.assertRaises(ConfigError, lambda: Service(name='_'))
  30. self.assertRaises(ConfigError, lambda: Service(name='____'))
  31. self.assertRaises(ConfigError, lambda: Service(name='foo_bar'))
  32. self.assertRaises(ConfigError, lambda: Service(name='__foo_bar__'))
  33. Service('a')
  34. Service('foo')
  35. def test_project_validation(self):
  36. self.assertRaises(ConfigError, lambda: Service(name='foo', project='_'))
  37. Service(name='foo', project='bar')
  38. def test_get_container_name(self):
  39. self.assertIsNone(get_container_name({}))
  40. self.assertEqual(get_container_name({'Name': 'myproject_db_1'}), 'myproject_db_1')
  41. self.assertEqual(get_container_name({'Names': ['/myproject_db_1', '/myproject_web_1/db']}), 'myproject_db_1')
  42. self.assertEqual(get_container_name({'Names': ['/swarm-host-1/myproject_db_1', '/swarm-host-1/myproject_web_1/db']}), 'myproject_db_1')
  43. def test_containers(self):
  44. service = Service('db', client=self.mock_client, project='myproject')
  45. self.mock_client.containers.return_value = []
  46. self.assertEqual(service.containers(), [])
  47. self.mock_client.containers.return_value = [
  48. {'Image': 'busybox', 'Id': 'OUT_1', 'Names': ['/myproject', '/foo/bar']},
  49. {'Image': 'busybox', 'Id': 'OUT_2', 'Names': ['/myproject_db']},
  50. {'Image': 'busybox', 'Id': 'OUT_3', 'Names': ['/db_1']},
  51. {'Image': 'busybox', 'Id': 'IN_1', 'Names': ['/myproject_db_1', '/myproject_web_1/db']},
  52. ]
  53. self.assertEqual([c.id for c in service.containers()], ['IN_1'])
  54. def test_containers_prefixed(self):
  55. service = Service('db', client=self.mock_client, project='myproject')
  56. self.mock_client.containers.return_value = [
  57. {'Image': 'busybox', 'Id': 'OUT_1', 'Names': ['/swarm-host-1/myproject', '/swarm-host-1/foo/bar']},
  58. {'Image': 'busybox', 'Id': 'OUT_2', 'Names': ['/swarm-host-1/myproject_db']},
  59. {'Image': 'busybox', 'Id': 'OUT_3', 'Names': ['/swarm-host-1/db_1']},
  60. {'Image': 'busybox', 'Id': 'IN_1', 'Names': ['/swarm-host-1/myproject_db_1', '/swarm-host-1/myproject_web_1/db']},
  61. ]
  62. self.assertEqual([c.id for c in service.containers()], ['IN_1'])
  63. def test_get_volumes_from_container(self):
  64. container_id = 'aabbccddee'
  65. service = Service(
  66. 'test',
  67. volumes_from=[mock.Mock(id=container_id, spec=Container)])
  68. self.assertEqual(service._get_volumes_from(), [container_id])
  69. def test_get_volumes_from_intermediate_container(self):
  70. container_id = 'aabbccddee'
  71. service = Service('test')
  72. container = mock.Mock(id=container_id, spec=Container)
  73. self.assertEqual(service._get_volumes_from(container), [container_id])
  74. def test_get_volumes_from_service_container_exists(self):
  75. container_ids = ['aabbccddee', '12345']
  76. from_service = mock.create_autospec(Service)
  77. from_service.containers.return_value = [
  78. mock.Mock(id=container_id, spec=Container)
  79. for container_id in container_ids
  80. ]
  81. service = Service('test', volumes_from=[from_service])
  82. self.assertEqual(service._get_volumes_from(), container_ids)
  83. def test_get_volumes_from_service_no_container(self):
  84. container_id = 'abababab'
  85. from_service = mock.create_autospec(Service)
  86. from_service.containers.return_value = []
  87. from_service.create_container.return_value = mock.Mock(
  88. id=container_id,
  89. spec=Container)
  90. service = Service('test', volumes_from=[from_service])
  91. self.assertEqual(service._get_volumes_from(), [container_id])
  92. from_service.create_container.assert_called_once_with()
  93. def test_split_port_with_host_ip(self):
  94. internal_port, external_port = split_port("127.0.0.1:1000:2000")
  95. self.assertEqual(internal_port, "2000")
  96. self.assertEqual(external_port, ("127.0.0.1", "1000"))
  97. def test_split_port_with_protocol(self):
  98. internal_port, external_port = split_port("127.0.0.1:1000:2000/udp")
  99. self.assertEqual(internal_port, "2000/udp")
  100. self.assertEqual(external_port, ("127.0.0.1", "1000"))
  101. def test_split_port_with_host_ip_no_port(self):
  102. internal_port, external_port = split_port("127.0.0.1::2000")
  103. self.assertEqual(internal_port, "2000")
  104. self.assertEqual(external_port, ("127.0.0.1", None))
  105. def test_split_port_with_host_port(self):
  106. internal_port, external_port = split_port("1000:2000")
  107. self.assertEqual(internal_port, "2000")
  108. self.assertEqual(external_port, "1000")
  109. def test_split_port_no_host_port(self):
  110. internal_port, external_port = split_port("2000")
  111. self.assertEqual(internal_port, "2000")
  112. self.assertEqual(external_port, None)
  113. def test_split_port_invalid(self):
  114. with self.assertRaises(ConfigError):
  115. split_port("0.0.0.0:1000:2000:tcp")
  116. def test_build_port_bindings_with_one_port(self):
  117. port_bindings = build_port_bindings(["127.0.0.1:1000:1000"])
  118. self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")])
  119. def test_build_port_bindings_with_matching_internal_ports(self):
  120. port_bindings = build_port_bindings(["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"])
  121. self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000"), ("127.0.0.1", "2000")])
  122. def test_build_port_bindings_with_nonmatching_internal_ports(self):
  123. port_bindings = build_port_bindings(["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"])
  124. self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")])
  125. self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")])
  126. def test_split_domainname_none(self):
  127. service = Service('foo', hostname='name', client=self.mock_client)
  128. self.mock_client.containers.return_value = []
  129. opts = service._get_container_create_options({'image': 'foo'})
  130. self.assertEqual(opts['hostname'], 'name', 'hostname')
  131. self.assertFalse('domainname' in opts, 'domainname')
  132. def test_split_domainname_fqdn(self):
  133. service = Service(
  134. 'foo',
  135. hostname='name.domain.tld',
  136. client=self.mock_client)
  137. self.mock_client.containers.return_value = []
  138. opts = service._get_container_create_options({'image': 'foo'})
  139. self.assertEqual(opts['hostname'], 'name', 'hostname')
  140. self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')
  141. def test_split_domainname_both(self):
  142. service = Service(
  143. 'foo',
  144. hostname='name',
  145. domainname='domain.tld',
  146. client=self.mock_client)
  147. self.mock_client.containers.return_value = []
  148. opts = service._get_container_create_options({'image': 'foo'})
  149. self.assertEqual(opts['hostname'], 'name', 'hostname')
  150. self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')
  151. def test_split_domainname_weird(self):
  152. service = Service(
  153. 'foo',
  154. hostname='name.sub',
  155. domainname='domain.tld',
  156. client=self.mock_client)
  157. self.mock_client.containers.return_value = []
  158. opts = service._get_container_create_options({'image': 'foo'})
  159. self.assertEqual(opts['hostname'], 'name.sub', 'hostname')
  160. self.assertEqual(opts['domainname'], 'domain.tld', 'domainname')
  161. def test_get_container_not_found(self):
  162. self.mock_client.containers.return_value = []
  163. service = Service('foo', client=self.mock_client)
  164. self.assertRaises(ValueError, service.get_container)
  165. @mock.patch('compose.service.Container', autospec=True)
  166. def test_get_container(self, mock_container_class):
  167. container_dict = dict(Name='default_foo_2')
  168. self.mock_client.containers.return_value = [container_dict]
  169. service = Service('foo', client=self.mock_client)
  170. container = service.get_container(number=2)
  171. self.assertEqual(container, mock_container_class.from_ps.return_value)
  172. mock_container_class.from_ps.assert_called_once_with(
  173. self.mock_client, container_dict)
  174. @mock.patch('compose.service.log', autospec=True)
  175. def test_pull_image(self, mock_log):
  176. service = Service('foo', client=self.mock_client, image='someimage:sometag')
  177. service.pull(insecure_registry=True)
  178. self.mock_client.pull.assert_called_once_with('someimage:sometag', insecure_registry=True)
  179. mock_log.info.assert_called_once_with('Pulling foo (someimage:sometag)...')
  180. @mock.patch('compose.service.Container', autospec=True)
  181. @mock.patch('compose.service.log', autospec=True)
  182. def test_create_container_from_insecure_registry(
  183. self,
  184. mock_log,
  185. mock_container):
  186. service = Service('foo', client=self.mock_client, image='someimage:sometag')
  187. mock_response = mock.Mock(Response)
  188. mock_response.status_code = 404
  189. mock_response.reason = "Not Found"
  190. mock_container.create.side_effect = APIError(
  191. 'Mock error', mock_response, "No such image")
  192. # We expect the APIError because our service requires a
  193. # non-existent image.
  194. with self.assertRaises(APIError):
  195. service.create_container(insecure_registry=True)
  196. self.mock_client.pull.assert_called_once_with(
  197. 'someimage:sometag',
  198. insecure_registry=True,
  199. stream=True)
  200. mock_log.info.assert_called_once_with(
  201. 'Pulling image someimage:sometag...')
  202. def test_parse_repository_tag(self):
  203. self.assertEqual(parse_repository_tag("root"), ("root", ""))
  204. self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag"))
  205. self.assertEqual(parse_repository_tag("user/repo"), ("user/repo", ""))
  206. self.assertEqual(parse_repository_tag("user/repo:tag"), ("user/repo", "tag"))
  207. self.assertEqual(parse_repository_tag("url:5000/repo"), ("url:5000/repo", ""))
  208. self.assertEqual(parse_repository_tag("url:5000/repo:tag"), ("url:5000/repo", "tag"))
  209. def test_latest_is_used_when_tag_is_not_specified(self):
  210. service = Service('foo', client=self.mock_client, image='someimage')
  211. Container.create = mock.Mock()
  212. service.create_container()
  213. self.assertEqual(Container.create.call_args[1]['image'], 'someimage:latest')
  214. def test_create_container_with_build(self):
  215. self.mock_client.images.return_value = []
  216. service = Service('foo', client=self.mock_client, build='.')
  217. service.build = mock.create_autospec(service.build)
  218. service.create_container(do_build=True)
  219. self.mock_client.images.assert_called_once_with(name=service.full_name)
  220. service.build.assert_called_once_with()
  221. def test_create_container_no_build(self):
  222. self.mock_client.images.return_value = []
  223. service = Service('foo', client=self.mock_client, build='.')
  224. service.create_container(do_build=False)
  225. self.assertFalse(self.mock_client.images.called)
  226. self.assertFalse(self.mock_client.build.called)
  227. class ServiceVolumesTest(unittest.TestCase):
  228. def test_parse_volume_spec_only_one_path(self):
  229. spec = parse_volume_spec('/the/volume')
  230. self.assertEqual(spec, (None, '/the/volume', 'rw'))
  231. def test_parse_volume_spec_internal_and_external(self):
  232. spec = parse_volume_spec('external:interval')
  233. self.assertEqual(spec, ('external', 'interval', 'rw'))
  234. def test_parse_volume_spec_with_mode(self):
  235. spec = parse_volume_spec('external:interval:ro')
  236. self.assertEqual(spec, ('external', 'interval', 'ro'))
  237. def test_parse_volume_spec_too_many_parts(self):
  238. with self.assertRaises(ConfigError):
  239. parse_volume_spec('one:two:three:four')
  240. def test_parse_volume_bad_mode(self):
  241. with self.assertRaises(ConfigError):
  242. parse_volume_spec('one:two:notrw')
  243. def test_build_volume_binding(self):
  244. binding = build_volume_binding(parse_volume_spec('/outside:/inside'))
  245. self.assertEqual(
  246. binding,
  247. ('/outside', dict(bind='/inside', ro=False)))
  248. @mock.patch.dict(os.environ)
  249. def test_build_volume_binding_with_environ(self):
  250. os.environ['VOLUME_PATH'] = '/opt'
  251. binding = build_volume_binding(parse_volume_spec('${VOLUME_PATH}:/opt'))
  252. self.assertEqual(binding, ('/opt', dict(bind='/opt', ro=False)))
  253. @mock.patch.dict(os.environ)
  254. def test_building_volume_binding_with_home(self):
  255. os.environ['HOME'] = '/home/user'
  256. binding = build_volume_binding(parse_volume_spec('~:/home/user'))
  257. self.assertEqual(
  258. binding,
  259. ('/home/user', dict(bind='/home/user', ro=False)))