test_object_store_service.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import filecmp
  2. import os
  3. import tempfile
  4. from datetime import datetime
  5. from io import BytesIO
  6. from unittest import skip
  7. import requests
  8. from cloudbridge.base import helpers as cb_helpers
  9. from cloudbridge.interfaces.exceptions import DuplicateResourceException
  10. from cloudbridge.interfaces.provider import TestMockHelperMixin
  11. from cloudbridge.interfaces.resources import Bucket
  12. from cloudbridge.interfaces.resources import BucketObject
  13. from cloudbridge.providers.aws.provider import AWSCloudProvider
  14. from cloudbridge.providers.gcp import GCPCloudProvider
  15. from tests import helpers
  16. from tests.helpers import ProviderTestBase
  17. from tests.helpers import standard_interface_tests as sit
  18. class CloudObjectStoreServiceTestCase(ProviderTestBase):
  19. _multiprocess_can_split_ = True
  20. @helpers.skipIfNoService(['storage._bucket_objects', 'storage.buckets'])
  21. def test_storage_services_event_pattern(self):
  22. # pylint:disable=protected-access
  23. self.assertEqual(
  24. self.provider.storage.buckets._service_event_pattern,
  25. "provider.storage.buckets",
  26. "Event pattern for {} service should be '{}', "
  27. "but found '{}'.".format("buckets",
  28. "provider.storage.buckets",
  29. self.provider.storage.buckets.
  30. _service_event_pattern))
  31. # pylint:disable=protected-access
  32. self.assertEqual(
  33. self.provider.storage._bucket_objects._service_event_pattern,
  34. "provider.storage._bucket_objects",
  35. "Event pattern for {} service should be '{}', "
  36. "but found '{}'.".format("bucket_objects",
  37. "provider.storage._bucket_objects",
  38. self.provider.storage._bucket_objects.
  39. _service_event_pattern))
  40. @helpers.skipIfNoService(['storage.buckets'])
  41. def test_crud_bucket(self):
  42. def create_bucket(name):
  43. return self.provider.storage.buckets.create(name)
  44. def cleanup_bucket(bucket):
  45. if bucket:
  46. bucket.delete()
  47. def extra_tests(bucket):
  48. # Recreating existing bucket should raise an exception
  49. with self.assertRaises(DuplicateResourceException):
  50. self.provider.storage.buckets.create(name=bucket.name)
  51. sit.check_crud(self, self.provider.storage.buckets, Bucket,
  52. "cb-crudbucket", create_bucket, cleanup_bucket,
  53. extra_test_func=extra_tests)
  54. @helpers.skipIfNoService(['storage.buckets'])
  55. def test_crud_bucket_object(self):
  56. test_bucket = None
  57. def create_bucket_obj(name):
  58. obj = test_bucket.objects.create(name)
  59. # TODO: This is wrong. We shouldn't have to have a separate
  60. # call to upload some content before being able to delete
  61. # the content. Maybe the create_object method should accept
  62. # the file content as a parameter.
  63. obj.upload("dummy content")
  64. return obj
  65. def cleanup_bucket_obj(bucket_obj):
  66. if bucket_obj:
  67. bucket_obj.delete()
  68. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  69. name = "cb-crudbucketobj-{0}".format(helpers.get_uuid())
  70. test_bucket = self.provider.storage.buckets.create(name)
  71. sit.check_crud(self, test_bucket.objects, BucketObject,
  72. "cb-bucketobj", create_bucket_obj,
  73. cleanup_bucket_obj, skip_name_check=True)
  74. @helpers.skipIfNoService(['storage.buckets'])
  75. def test_crud_bucket_object_properties(self):
  76. # Create a new bucket, upload some contents into the bucket, and
  77. # check whether list properly detects the new content.
  78. # Delete everything afterwards.
  79. name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
  80. test_bucket = self.provider.storage.buckets.create(name)
  81. # ensure that the bucket is empty
  82. objects = test_bucket.objects.list()
  83. self.assertEqual([], objects)
  84. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  85. obj_name_prefix = "hello"
  86. obj_name = obj_name_prefix + "_world.txt"
  87. obj = test_bucket.objects.create(obj_name)
  88. with cb_helpers.cleanup_action(lambda: obj.delete()):
  89. # TODO: This is wrong. We shouldn't have to have a separate
  90. # call to upload some content before being able to delete
  91. # the content. Maybe the create_object method should accept
  92. # the file content as a parameter.
  93. obj.upload("dummy content")
  94. objs = test_bucket.objects.list()
  95. self.assertTrue(
  96. isinstance(objs[0].size, int),
  97. "Object size property needs to be a int, not {0}".format(
  98. type(objs[0].size)))
  99. # GET an object as the size property implementation differs
  100. # for objects returned by LIST and GET.
  101. obj = test_bucket.objects.get(objs[0].id)
  102. self.assertTrue(
  103. isinstance(objs[0].size, int),
  104. "Object size property needs to be an int, not {0}".format(
  105. type(obj.size)))
  106. self.assertTrue(
  107. datetime.strptime(objs[0].last_modified[:23],
  108. "%Y-%m-%dT%H:%M:%S.%f"),
  109. "Object's last_modified field format {0} not matching."
  110. .format(objs[0].last_modified))
  111. # check iteration
  112. iter_objs = list(test_bucket.objects)
  113. self.assertListEqual(iter_objs, objs)
  114. obj_too = test_bucket.objects.get(obj_name)
  115. self.assertTrue(
  116. isinstance(obj_too, BucketObject),
  117. "Did not get object {0} of expected type.".format(obj_too))
  118. prefix_filtered_list = test_bucket.objects.list(
  119. prefix=obj_name_prefix)
  120. self.assertTrue(
  121. len(objs) == len(prefix_filtered_list) == 1,
  122. 'The number of objects returned by list function, '
  123. 'with and without a prefix, are expected to be equal, '
  124. 'but its detected otherwise.')
  125. sit.check_delete(self, test_bucket.objects, obj)
  126. @helpers.skipIfNoService(['storage.buckets'])
  127. def test_upload_download_bucket_content(self):
  128. name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
  129. test_bucket = self.provider.storage.buckets.create(name)
  130. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  131. obj_name = "hello_upload_download.txt"
  132. obj = test_bucket.objects.create(obj_name)
  133. with cb_helpers.cleanup_action(lambda: obj.delete()):
  134. content = b"Hello World. Here's some content."
  135. # TODO: Upload and download methods accept different parameter
  136. # types. Need to make this consistent - possibly provider
  137. # multiple methods like upload_from_file, from_stream etc.
  138. obj.upload(content)
  139. target_stream = BytesIO()
  140. obj.save_content(target_stream)
  141. self.assertEqual(target_stream.getvalue(), content)
  142. target_stream2 = BytesIO()
  143. for data in obj.iter_content():
  144. target_stream2.write(data)
  145. self.assertEqual(target_stream2.getvalue(), content)
  146. @helpers.skipIfNoService(['storage.buckets'])
  147. def test_generate_url(self):
  148. name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
  149. test_bucket = self.provider.storage.buckets.create(name)
  150. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  151. obj_name = "hello_upload_download.txt"
  152. obj = test_bucket.objects.create(obj_name)
  153. with cb_helpers.cleanup_action(lambda: obj.delete()):
  154. content = b"Hello World. Generate a url."
  155. obj.upload(content)
  156. target_stream = BytesIO()
  157. obj.save_content(target_stream)
  158. url = obj.generate_url(100)
  159. if isinstance(self.provider, TestMockHelperMixin):
  160. raise self.skipTest(
  161. "Skipping rest of test - mock providers can't"
  162. " access generated url")
  163. self.assertEqual(requests.get(url).content, content)
  164. @helpers.skipIfNoService(['storage.buckets'])
  165. def test_generate_url_write_permissions(self):
  166. name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
  167. test_bucket = self.provider.storage.buckets.create(name)
  168. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  169. obj_name = "hello_upload_download.txt"
  170. obj = test_bucket.objects.create(obj_name)
  171. with cb_helpers.cleanup_action(lambda: obj.delete()):
  172. content = b"Hello World. Generate a url."
  173. url = obj.generate_url(100, writable=True)
  174. if isinstance(self.provider, TestMockHelperMixin):
  175. raise self.skipTest(
  176. "Skipping rest of test - mock providers can't"
  177. " access generated url")
  178. elif isinstance(self.provider, GCPCloudProvider):
  179. requests.put(url, data=content)
  180. elif isinstance(self.provider, AWSCloudProvider):
  181. requests.put(url['url'], data=url['fields'], files={"file": (obj_name, content)})
  182. else:
  183. requests.post(url, data=content)
  184. obj = test_bucket.objects.get(obj_name)
  185. obj_content = [content for content in obj.iter_content()]
  186. self.assertEqual(obj_content[0], content)
  187. @helpers.skipIfNoService(['storage.buckets'])
  188. def test_upload_download_bucket_content_from_file(self):
  189. name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
  190. test_bucket = self.provider.storage.buckets.create(name)
  191. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  192. obj_name = "hello_upload_download.txt"
  193. obj = test_bucket.objects.create(obj_name)
  194. with cb_helpers.cleanup_action(lambda: obj.delete()):
  195. test_file = os.path.join(
  196. helpers.get_test_fixtures_folder(), 'logo.jpg')
  197. obj.upload_from_file(test_file)
  198. target_stream = BytesIO()
  199. obj.save_content(target_stream)
  200. with open(test_file, 'rb') as f:
  201. self.assertEqual(target_stream.getvalue(), f.read())
  202. @skip("Skip unless you want to test objects bigger than 5GB")
  203. @helpers.skipIfNoService(['storage.buckets'])
  204. def test_upload_download_bucket_content_with_large_file(self):
  205. # Creates a 6 Gig file in the temp directory, then uploads it to
  206. # Swift. Once uploaded, then downloads to a new file in the temp
  207. # directory and compares the two files to see if they match.
  208. temp_dir = tempfile.gettempdir()
  209. file_name = '6GigTest.tmp'
  210. six_gig_file = os.path.join(temp_dir, file_name)
  211. with open(six_gig_file, "wb") as out:
  212. out.truncate(6 * 1024 * 1024 * 1024) # 6 Gig...
  213. with cb_helpers.cleanup_action(lambda: os.remove(six_gig_file)):
  214. download_file = "{0}/cbtestfile-{1}".format(temp_dir, file_name)
  215. bucket_name = "cbtestbucketlargeobjs-{0}".format(
  216. helpers.get_uuid())
  217. test_bucket = self.provider.storage.buckets.create(bucket_name)
  218. with cb_helpers.cleanup_action(lambda: test_bucket.delete()):
  219. test_obj = test_bucket.objects.create(file_name)
  220. with cb_helpers.cleanup_action(lambda: test_obj.delete()):
  221. file_uploaded = test_obj.upload_from_file(six_gig_file)
  222. self.assertTrue(file_uploaded, "Could not upload object?")
  223. with cb_helpers.cleanup_action(
  224. lambda: os.remove(download_file)):
  225. with open(download_file, 'wb') as f:
  226. test_obj.save_content(f)
  227. self.assertTrue(
  228. filecmp.cmp(six_gig_file, download_file),
  229. "Uploaded file != downloaded")