test_swift.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. # test_swift.py -- Unittests for the Swift backend.
  2. # Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
  3. #
  4. # Author: Fabien Boucher <fabien.boucher@enovance.com>
  5. #
  6. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  7. # General Public License as public by the Free Software Foundation; version 2.0
  8. # or (at your option) any later version. You can redistribute it and/or
  9. # modify it under the terms of either of these two licenses.
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. # You should have received a copy of the licenses; if not, see
  18. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  19. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  20. # License, Version 2.0.
  21. #
  22. """Tests for dulwich.contrib.swift."""
  23. import posixpath
  24. from time import time
  25. from io import BytesIO, StringIO
  26. from unittest import skipIf
  27. from dulwich.tests import (
  28. TestCase,
  29. )
  30. from dulwich.tests.test_object_store import (
  31. ObjectStoreTests,
  32. )
  33. from dulwich.objects import (
  34. Blob,
  35. Commit,
  36. Tree,
  37. Tag,
  38. parse_timezone,
  39. )
  40. import json
  41. missing_libs = []
  42. try:
  43. import gevent # noqa:F401
  44. except ImportError:
  45. missing_libs.append("gevent")
  46. try:
  47. import geventhttpclient # noqa:F401
  48. except ImportError:
  49. missing_libs.append("geventhttpclient")
  50. try:
  51. from mock import patch
  52. except ImportError:
  53. missing_libs.append("mock")
  54. skipmsg = "Required libraries are not installed (%r)" % missing_libs
  55. if not missing_libs:
  56. from dulwich.contrib import swift
  57. config_file = """[swift]
  58. auth_url = http://127.0.0.1:8080/auth/%(version_str)s
  59. auth_ver = %(version_int)s
  60. username = test;tester
  61. password = testing
  62. region_name = %(region_name)s
  63. endpoint_type = %(endpoint_type)s
  64. concurrency = %(concurrency)s
  65. chunk_length = %(chunk_length)s
  66. cache_length = %(cache_length)s
  67. http_pool_length = %(http_pool_length)s
  68. http_timeout = %(http_timeout)s
  69. """
  70. def_config_file = {'version_str': 'v1.0',
  71. 'version_int': 1,
  72. 'concurrency': 1,
  73. 'chunk_length': 12228,
  74. 'cache_length': 1,
  75. 'region_name': 'test',
  76. 'endpoint_type': 'internalURL',
  77. 'http_pool_length': 1,
  78. 'http_timeout': 1}
  79. def create_swift_connector(store={}):
  80. return lambda root, conf: FakeSwiftConnector(root,
  81. conf=conf,
  82. store=store)
  83. class Response(object):
  84. def __init__(self, headers={}, status=200, content=None):
  85. self.headers = headers
  86. self.status_code = status
  87. self.content = content
  88. def __getitem__(self, key):
  89. return self.headers[key]
  90. def items(self):
  91. return self.headers.items()
  92. def read(self):
  93. return self.content
  94. def fake_auth_request_v1(*args, **kwargs):
  95. ret = Response({'X-Storage-Url':
  96. 'http://127.0.0.1:8080/v1.0/AUTH_fakeuser',
  97. 'X-Auth-Token': '12' * 10},
  98. 200)
  99. return ret
  100. def fake_auth_request_v1_error(*args, **kwargs):
  101. ret = Response({},
  102. 401)
  103. return ret
  104. def fake_auth_request_v2(*args, **kwargs):
  105. s_url = 'http://127.0.0.1:8080/v1.0/AUTH_fakeuser'
  106. resp = {'access': {'token': {'id': '12' * 10},
  107. 'serviceCatalog':
  108. [
  109. {'type': 'object-store',
  110. 'endpoints': [{'region': 'test',
  111. 'internalURL': s_url,
  112. },
  113. ]
  114. },
  115. ]
  116. }
  117. }
  118. ret = Response(status=200, content=json.dumps(resp))
  119. return ret
  120. def create_commit(data, marker=b'Default', blob=None):
  121. if not blob:
  122. blob = Blob.from_string(b'The blob content ' + marker)
  123. tree = Tree()
  124. tree.add(b"thefile_" + marker, 0o100644, blob.id)
  125. cmt = Commit()
  126. if data:
  127. assert isinstance(data[-1], Commit)
  128. cmt.parents = [data[-1].id]
  129. cmt.tree = tree.id
  130. author = b"John Doe " + marker + b" <john@doe.net>"
  131. cmt.author = cmt.committer = author
  132. tz = parse_timezone(b'-0200')[0]
  133. cmt.commit_time = cmt.author_time = int(time())
  134. cmt.commit_timezone = cmt.author_timezone = tz
  135. cmt.encoding = b"UTF-8"
  136. cmt.message = b"The commit message " + marker
  137. tag = Tag()
  138. tag.tagger = b"john@doe.net"
  139. tag.message = b"Annotated tag"
  140. tag.tag_timezone = parse_timezone(b'-0200')[0]
  141. tag.tag_time = cmt.author_time
  142. tag.object = (Commit, cmt.id)
  143. tag.name = b"v_" + marker + b"_0.1"
  144. return blob, tree, tag, cmt
  145. def create_commits(length=1, marker=b'Default'):
  146. data = []
  147. for i in range(0, length):
  148. _marker = ("%s_%s" % (marker, i)).encode()
  149. blob, tree, tag, cmt = create_commit(data, _marker)
  150. data.extend([blob, tree, tag, cmt])
  151. return data
  152. @skipIf(missing_libs, skipmsg)
  153. class FakeSwiftConnector(object):
  154. def __init__(self, root, conf, store=None):
  155. if store:
  156. self.store = store
  157. else:
  158. self.store = {}
  159. self.conf = conf
  160. self.root = root
  161. self.concurrency = 1
  162. self.chunk_length = 12228
  163. self.cache_length = 1
  164. def put_object(self, name, content):
  165. name = posixpath.join(self.root, name)
  166. if hasattr(content, 'seek'):
  167. content.seek(0)
  168. content = content.read()
  169. self.store[name] = content
  170. def get_object(self, name, range=None):
  171. name = posixpath.join(self.root, name)
  172. if not range:
  173. try:
  174. return BytesIO(self.store[name])
  175. except KeyError:
  176. return None
  177. else:
  178. l, r = range.split('-')
  179. try:
  180. if not l:
  181. r = -int(r)
  182. return self.store[name][r:]
  183. else:
  184. return self.store[name][int(l):int(r)]
  185. except KeyError:
  186. return None
  187. def get_container_objects(self):
  188. return [{'name': k.replace(self.root + '/', '')}
  189. for k in self.store]
  190. def create_root(self):
  191. if self.root in self.store.keys():
  192. pass
  193. else:
  194. self.store[self.root] = ''
  195. def get_object_stat(self, name):
  196. name = posixpath.join(self.root, name)
  197. if name not in self.store:
  198. return None
  199. return {'content-length': len(self.store[name])}
  200. @skipIf(missing_libs, skipmsg)
  201. class TestSwiftRepo(TestCase):
  202. def setUp(self):
  203. super(TestSwiftRepo, self).setUp()
  204. self.conf = swift.load_conf(file=StringIO(config_file %
  205. def_config_file))
  206. def test_init(self):
  207. store = {'fakerepo/objects/pack': ''}
  208. with patch('dulwich.contrib.swift.SwiftConnector',
  209. new_callable=create_swift_connector,
  210. store=store):
  211. swift.SwiftRepo('fakerepo', conf=self.conf)
  212. def test_init_no_data(self):
  213. with patch('dulwich.contrib.swift.SwiftConnector',
  214. new_callable=create_swift_connector):
  215. self.assertRaises(Exception, swift.SwiftRepo,
  216. 'fakerepo', self.conf)
  217. def test_init_bad_data(self):
  218. store = {'fakerepo/.git/objects/pack': ''}
  219. with patch('dulwich.contrib.swift.SwiftConnector',
  220. new_callable=create_swift_connector,
  221. store=store):
  222. self.assertRaises(Exception, swift.SwiftRepo,
  223. 'fakerepo', self.conf)
  224. def test_put_named_file(self):
  225. store = {'fakerepo/objects/pack': ''}
  226. with patch('dulwich.contrib.swift.SwiftConnector',
  227. new_callable=create_swift_connector,
  228. store=store):
  229. repo = swift.SwiftRepo('fakerepo', conf=self.conf)
  230. desc = b'Fake repo'
  231. repo._put_named_file('description', desc)
  232. self.assertEqual(repo.scon.store['fakerepo/description'],
  233. desc)
  234. def test_init_bare(self):
  235. fsc = FakeSwiftConnector('fakeroot', conf=self.conf)
  236. with patch('dulwich.contrib.swift.SwiftConnector',
  237. new_callable=create_swift_connector,
  238. store=fsc.store):
  239. swift.SwiftRepo.init_bare(fsc, conf=self.conf)
  240. self.assertIn('fakeroot/objects/pack', fsc.store)
  241. self.assertIn('fakeroot/info/refs', fsc.store)
  242. self.assertIn('fakeroot/description', fsc.store)
  243. @skipIf(missing_libs, skipmsg)
  244. class TestSwiftInfoRefsContainer(TestCase):
  245. def setUp(self):
  246. super(TestSwiftInfoRefsContainer, self).setUp()
  247. content = (
  248. b"22effb216e3a82f97da599b8885a6cadb488b4c5\trefs/heads/master\n"
  249. b"cca703b0e1399008b53a1a236d6b4584737649e4\trefs/heads/dev")
  250. self.store = {'fakerepo/info/refs': content}
  251. self.conf = swift.load_conf(file=StringIO(config_file %
  252. def_config_file))
  253. self.fsc = FakeSwiftConnector('fakerepo', conf=self.conf)
  254. self.object_store = {}
  255. def test_init(self):
  256. """info/refs does not exists"""
  257. irc = swift.SwiftInfoRefsContainer(self.fsc, self.object_store)
  258. self.assertEqual(len(irc._refs), 0)
  259. self.fsc.store = self.store
  260. irc = swift.SwiftInfoRefsContainer(self.fsc, self.object_store)
  261. self.assertIn(b'refs/heads/dev', irc.allkeys())
  262. self.assertIn(b'refs/heads/master', irc.allkeys())
  263. def test_set_if_equals(self):
  264. self.fsc.store = self.store
  265. irc = swift.SwiftInfoRefsContainer(self.fsc, self.object_store)
  266. irc.set_if_equals(b'refs/heads/dev',
  267. b"cca703b0e1399008b53a1a236d6b4584737649e4", b'1'*40)
  268. self.assertEqual(irc[b'refs/heads/dev'], b'1'*40)
  269. def test_remove_if_equals(self):
  270. self.fsc.store = self.store
  271. irc = swift.SwiftInfoRefsContainer(self.fsc, self.object_store)
  272. irc.remove_if_equals(b'refs/heads/dev',
  273. b"cca703b0e1399008b53a1a236d6b4584737649e4")
  274. self.assertNotIn(b'refs/heads/dev', irc.allkeys())
  275. @skipIf(missing_libs, skipmsg)
  276. class TestSwiftConnector(TestCase):
  277. def setUp(self):
  278. super(TestSwiftConnector, self).setUp()
  279. self.conf = swift.load_conf(file=StringIO(config_file %
  280. def_config_file))
  281. with patch('geventhttpclient.HTTPClient.request',
  282. fake_auth_request_v1):
  283. self.conn = swift.SwiftConnector('fakerepo', conf=self.conf)
  284. def test_init_connector(self):
  285. self.assertEqual(self.conn.auth_ver, '1')
  286. self.assertEqual(self.conn.auth_url,
  287. 'http://127.0.0.1:8080/auth/v1.0')
  288. self.assertEqual(self.conn.user, 'test:tester')
  289. self.assertEqual(self.conn.password, 'testing')
  290. self.assertEqual(self.conn.root, 'fakerepo')
  291. self.assertEqual(self.conn.storage_url,
  292. 'http://127.0.0.1:8080/v1.0/AUTH_fakeuser')
  293. self.assertEqual(self.conn.token, '12' * 10)
  294. self.assertEqual(self.conn.http_timeout, 1)
  295. self.assertEqual(self.conn.http_pool_length, 1)
  296. self.assertEqual(self.conn.concurrency, 1)
  297. self.conf.set('swift', 'auth_ver', '2')
  298. self.conf.set('swift', 'auth_url', 'http://127.0.0.1:8080/auth/v2.0')
  299. with patch('geventhttpclient.HTTPClient.request',
  300. fake_auth_request_v2):
  301. conn = swift.SwiftConnector('fakerepo', conf=self.conf)
  302. self.assertEqual(conn.user, 'tester')
  303. self.assertEqual(conn.tenant, 'test')
  304. self.conf.set('swift', 'auth_ver', '1')
  305. self.conf.set('swift', 'auth_url', 'http://127.0.0.1:8080/auth/v1.0')
  306. with patch('geventhttpclient.HTTPClient.request',
  307. fake_auth_request_v1_error):
  308. self.assertRaises(swift.SwiftException,
  309. lambda: swift.SwiftConnector('fakerepo',
  310. conf=self.conf))
  311. def test_root_exists(self):
  312. with patch('geventhttpclient.HTTPClient.request',
  313. lambda *args: Response()):
  314. self.assertEqual(self.conn.test_root_exists(), True)
  315. def test_root_not_exists(self):
  316. with patch('geventhttpclient.HTTPClient.request',
  317. lambda *args: Response(status=404)):
  318. self.assertEqual(self.conn.test_root_exists(), None)
  319. def test_create_root(self):
  320. with patch('dulwich.contrib.swift.SwiftConnector.test_root_exists',
  321. lambda *args: None):
  322. with patch('geventhttpclient.HTTPClient.request',
  323. lambda *args: Response()):
  324. self.assertEqual(self.conn.create_root(), None)
  325. def test_create_root_fails(self):
  326. with patch('dulwich.contrib.swift.SwiftConnector.test_root_exists',
  327. lambda *args: None):
  328. with patch('geventhttpclient.HTTPClient.request',
  329. lambda *args: Response(status=404)):
  330. self.assertRaises(swift.SwiftException,
  331. lambda: self.conn.create_root())
  332. def test_get_container_objects(self):
  333. with patch('geventhttpclient.HTTPClient.request',
  334. lambda *args: Response(content=json.dumps(
  335. (({'name': 'a'}, {'name': 'b'}))))):
  336. self.assertEqual(len(self.conn.get_container_objects()), 2)
  337. def test_get_container_objects_fails(self):
  338. with patch('geventhttpclient.HTTPClient.request',
  339. lambda *args: Response(status=404)):
  340. self.assertEqual(self.conn.get_container_objects(), None)
  341. def test_get_object_stat(self):
  342. with patch('geventhttpclient.HTTPClient.request',
  343. lambda *args: Response(headers={'content-length': '10'})):
  344. self.assertEqual(self.conn.get_object_stat('a')['content-length'],
  345. '10')
  346. def test_get_object_stat_fails(self):
  347. with patch('geventhttpclient.HTTPClient.request',
  348. lambda *args: Response(status=404)):
  349. self.assertEqual(self.conn.get_object_stat('a'), None)
  350. def test_put_object(self):
  351. with patch('geventhttpclient.HTTPClient.request',
  352. lambda *args, **kwargs: Response()):
  353. self.assertEqual(self.conn.put_object('a', BytesIO(b'content')),
  354. None)
  355. def test_put_object_fails(self):
  356. with patch('geventhttpclient.HTTPClient.request',
  357. lambda *args, **kwargs: Response(status=400)):
  358. self.assertRaises(swift.SwiftException,
  359. lambda: self.conn.put_object(
  360. 'a', BytesIO(b'content')))
  361. def test_get_object(self):
  362. with patch('geventhttpclient.HTTPClient.request',
  363. lambda *args, **kwargs: Response(content=b'content')):
  364. self.assertEqual(self.conn.get_object('a').read(), b'content')
  365. with patch('geventhttpclient.HTTPClient.request',
  366. lambda *args, **kwargs: Response(content=b'content')):
  367. self.assertEqual(
  368. self.conn.get_object('a', range='0-6'),
  369. b'content')
  370. def test_get_object_fails(self):
  371. with patch('geventhttpclient.HTTPClient.request',
  372. lambda *args, **kwargs: Response(status=404)):
  373. self.assertEqual(self.conn.get_object('a'), None)
  374. def test_del_object(self):
  375. with patch('geventhttpclient.HTTPClient.request',
  376. lambda *args: Response()):
  377. self.assertEqual(self.conn.del_object('a'), None)
  378. def test_del_root(self):
  379. with patch('dulwich.contrib.swift.SwiftConnector.del_object',
  380. lambda *args: None):
  381. with patch('dulwich.contrib.swift.SwiftConnector.'
  382. 'get_container_objects',
  383. lambda *args: ({'name': 'a'}, {'name': 'b'})):
  384. with patch('geventhttpclient.HTTPClient.request',
  385. lambda *args: Response()):
  386. self.assertEqual(self.conn.del_root(), None)
  387. @skipIf(missing_libs, skipmsg)
  388. class SwiftObjectStoreTests(ObjectStoreTests, TestCase):
  389. def setUp(self):
  390. TestCase.setUp(self)
  391. conf = swift.load_conf(file=StringIO(config_file %
  392. def_config_file))
  393. fsc = FakeSwiftConnector('fakerepo', conf=conf)
  394. self.store = swift.SwiftObjectStore(fsc)