PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/boto-2.5.2/tests/integration/dynamodb/test_layer2.py

#
Python | 383 lines | 353 code | 5 blank | 25 comment | 1 complexity | c148948baa196839035f04e380c8066b MD5 | raw file
  1. # Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
  2. # All rights reserved.
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining a
  5. # copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish, dis-
  8. # tribute, sublicense, and/or sell copies of the Software, and to permit
  9. # persons to whom the Software is furnished to do so, subject to the fol-
  10. # lowing conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included
  13. # in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  16. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  17. # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  18. # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  19. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21. # IN THE SOFTWARE.
  22. """
  23. Tests for Layer2 of Amazon DynamoDB
  24. """
  25. import unittest
  26. import time
  27. import uuid
  28. from boto.dynamodb.exceptions import DynamoDBKeyNotFoundError, DynamoDBItemError
  29. from boto.dynamodb.exceptions import DynamoDBConditionalCheckFailedError
  30. from boto.dynamodb.layer2 import Layer2
  31. from boto.dynamodb.types import get_dynamodb_type
  32. from boto.dynamodb.condition import *
  33. class DynamoDBLayer2Test (unittest.TestCase):
  34. dynamodb = True
  35. def test_layer2_basic(self):
  36. print '--- running Amazon DynamoDB Layer2 tests ---'
  37. c = Layer2()
  38. # First create a schema for the table
  39. hash_key_name = 'forum_name'
  40. hash_key_proto_value = ''
  41. range_key_name = 'subject'
  42. range_key_proto_value = ''
  43. schema = c.create_schema(hash_key_name, hash_key_proto_value,
  44. range_key_name, range_key_proto_value)
  45. # Create another schema without a range key
  46. schema2 = c.create_schema('post_id', '')
  47. # Now create a table
  48. index = int(time.time())
  49. table_name = 'test-%d' % index
  50. read_units = 5
  51. write_units = 5
  52. table = c.create_table(table_name, schema, read_units, write_units)
  53. assert table.name == table_name
  54. assert table.schema.hash_key_name == hash_key_name
  55. assert table.schema.hash_key_type == get_dynamodb_type(hash_key_proto_value)
  56. assert table.schema.range_key_name == range_key_name
  57. assert table.schema.range_key_type == get_dynamodb_type(range_key_proto_value)
  58. assert table.read_units == read_units
  59. assert table.write_units == write_units
  60. assert table.item_count == 0
  61. assert table.size_bytes == 0
  62. # Create the second table
  63. table2_name = 'test-%d' % (index + 1)
  64. table2 = c.create_table(table2_name, schema2, read_units, write_units)
  65. # Wait for table to become active
  66. table.refresh(wait_for_active=True)
  67. table2.refresh(wait_for_active=True)
  68. # List tables and make sure new one is there
  69. table_names = c.list_tables()
  70. assert table_name in table_names
  71. assert table2_name in table_names
  72. # Update the tables ProvisionedThroughput
  73. new_read_units = 10
  74. new_write_units = 5
  75. table.update_throughput(new_read_units, new_write_units)
  76. # Wait for table to be updated
  77. table.refresh(wait_for_active=True)
  78. assert table.read_units == new_read_units
  79. assert table.write_units == new_write_units
  80. # Put an item
  81. item1_key = 'Amazon DynamoDB'
  82. item1_range = 'DynamoDB Thread 1'
  83. item1_attrs = {
  84. 'Message': 'DynamoDB thread 1 message text',
  85. 'LastPostedBy': 'User A',
  86. 'Views': 0,
  87. 'Replies': 0,
  88. 'Answered': 0,
  89. 'Public': True,
  90. 'Tags': set(['index', 'primarykey', 'table']),
  91. 'LastPostDateTime': '12/9/2011 11:36:03 PM'}
  92. # Test a few corner cases with new_item
  93. # Try supplying a hash_key as an arg and as an item in attrs
  94. item1_attrs[hash_key_name] = 'foo'
  95. foobar_item = table.new_item(item1_key, item1_range, item1_attrs)
  96. assert foobar_item.hash_key == item1_key
  97. # Try supplying a range_key as an arg and as an item in attrs
  98. item1_attrs[range_key_name] = 'bar'
  99. foobar_item = table.new_item(item1_key, item1_range, item1_attrs)
  100. assert foobar_item.range_key == item1_range
  101. # Try supplying hash and range key in attrs dict
  102. foobar_item = table.new_item(attrs=item1_attrs)
  103. assert foobar_item.hash_key == 'foo'
  104. assert foobar_item.range_key == 'bar'
  105. del item1_attrs[hash_key_name]
  106. del item1_attrs[range_key_name]
  107. item1 = table.new_item(item1_key, item1_range, item1_attrs)
  108. # make sure the put() succeeds
  109. try:
  110. item1.put()
  111. except c.layer1.ResponseError, e:
  112. raise Exception("Item put failed: %s" % e)
  113. # Try to get an item that does not exist.
  114. self.assertRaises(DynamoDBKeyNotFoundError,
  115. table.get_item, 'bogus_key', item1_range)
  116. # Now do a consistent read and check results
  117. item1_copy = table.get_item(item1_key, item1_range,
  118. consistent_read=True)
  119. assert item1_copy.hash_key == item1.hash_key
  120. assert item1_copy.range_key == item1.range_key
  121. for attr_name in item1_copy:
  122. val = item1_copy[attr_name]
  123. if isinstance(val, (int, long, float, basestring)):
  124. assert val == item1[attr_name]
  125. # Try retrieving only select attributes
  126. attributes = ['Message', 'Views']
  127. item1_small = table.get_item(item1_key, item1_range,
  128. attributes_to_get=attributes,
  129. consistent_read=True)
  130. for attr_name in item1_small:
  131. # The item will include the attributes we asked for as
  132. # well as the hashkey and rangekey, so filter those out.
  133. if attr_name not in (item1_small.hash_key_name,
  134. item1_small.range_key_name):
  135. assert attr_name in attributes
  136. self.assertTrue(table.has_item(item1_key, range_key=item1_range,
  137. consistent_read=True))
  138. # Try to delete the item with the wrong Expected value
  139. expected = {'Views': 1}
  140. self.assertRaises(DynamoDBConditionalCheckFailedError,
  141. item1.delete, expected_value=expected)
  142. # Try to delete a value while expecting a non-existant attribute
  143. expected = {'FooBar': True}
  144. try:
  145. item1.delete(expected_value=expected)
  146. except c.layer1.ResponseError, e:
  147. pass
  148. # Now update the existing object
  149. item1.add_attribute('Replies', 2)
  150. removed_attr = 'Public'
  151. item1.delete_attribute(removed_attr)
  152. removed_tag = item1_attrs['Tags'].copy().pop()
  153. item1.delete_attribute('Tags', set([removed_tag]))
  154. replies_by_set = set(['Adam', 'Arnie'])
  155. item1.put_attribute('RepliesBy', replies_by_set)
  156. retvals = item1.save(return_values='ALL_OLD')
  157. # Need more tests here for variations on return_values
  158. assert 'Attributes' in retvals
  159. # Check for correct updates
  160. item1_updated = table.get_item(item1_key, item1_range,
  161. consistent_read=True)
  162. assert item1_updated['Replies'] == item1_attrs['Replies'] + 2
  163. self.assertFalse(removed_attr in item1_updated)
  164. self.assertTrue(removed_tag not in item1_updated['Tags'])
  165. self.assertTrue('RepliesBy' in item1_updated)
  166. self.assertTrue(item1_updated['RepliesBy'] == replies_by_set)
  167. # Put a few more items into the table
  168. item2_key = 'Amazon DynamoDB'
  169. item2_range = 'DynamoDB Thread 2'
  170. item2_attrs = {
  171. 'Message': 'DynamoDB thread 2 message text',
  172. 'LastPostedBy': 'User A',
  173. 'Views': 0,
  174. 'Replies': 0,
  175. 'Answered': 0,
  176. 'Tags': set(["index", "primarykey", "table"]),
  177. 'LastPost2DateTime': '12/9/2011 11:36:03 PM'}
  178. item2 = table.new_item(item2_key, item2_range, item2_attrs)
  179. item2.put()
  180. item3_key = 'Amazon S3'
  181. item3_range = 'S3 Thread 1'
  182. item3_attrs = {
  183. 'Message': 'S3 Thread 1 message text',
  184. 'LastPostedBy': 'User A',
  185. 'Views': 0,
  186. 'Replies': 0,
  187. 'Answered': 0,
  188. 'Tags': set(['largeobject', 'multipart upload']),
  189. 'LastPostDateTime': '12/9/2011 11:36:03 PM'
  190. }
  191. item3 = table.new_item(item3_key, item3_range, item3_attrs)
  192. item3.put()
  193. # Put an item into the second table
  194. table2_item1_key = uuid.uuid4().hex
  195. table2_item1_attrs = {
  196. 'DateTimePosted': '25/1/2011 12:34:56 PM',
  197. 'Text': 'I think boto rocks and so does DynamoDB'
  198. }
  199. table2_item1 = table2.new_item(table2_item1_key,
  200. attrs=table2_item1_attrs)
  201. table2_item1.put()
  202. # Try a few queries
  203. items = table.query('Amazon DynamoDB', BEGINS_WITH('DynamoDB'))
  204. n = 0
  205. for item in items:
  206. n += 1
  207. assert n == 2
  208. assert items.consumed_units > 0
  209. items = table.query('Amazon DynamoDB', BEGINS_WITH('DynamoDB'),
  210. request_limit=1, max_results=1)
  211. n = 0
  212. for item in items:
  213. n += 1
  214. assert n == 1
  215. assert items.consumed_units > 0
  216. # Try a few scans
  217. items = table.scan()
  218. n = 0
  219. for item in items:
  220. n += 1
  221. assert n == 3
  222. assert items.consumed_units > 0
  223. items = table.scan({'Replies': GT(0)})
  224. n = 0
  225. for item in items:
  226. n += 1
  227. assert n == 1
  228. assert items.consumed_units > 0
  229. # Test some integer and float attributes
  230. integer_value = 42
  231. float_value = 345.678
  232. item3['IntAttr'] = integer_value
  233. item3['FloatAttr'] = float_value
  234. # Test booleans
  235. item3['TrueBoolean'] = True
  236. item3['FalseBoolean'] = False
  237. # Test some set values
  238. integer_set = set([1, 2, 3, 4, 5])
  239. float_set = set([1.1, 2.2, 3.3, 4.4, 5.5])
  240. mixed_set = set([1, 2, 3.3, 4, 5.555])
  241. str_set = set(['foo', 'bar', 'fie', 'baz'])
  242. item3['IntSetAttr'] = integer_set
  243. item3['FloatSetAttr'] = float_set
  244. item3['MixedSetAttr'] = mixed_set
  245. item3['StrSetAttr'] = str_set
  246. item3.put()
  247. # Now do a consistent read
  248. item4 = table.get_item(item3_key, item3_range, consistent_read=True)
  249. assert item4['IntAttr'] == integer_value
  250. assert item4['FloatAttr'] == float_value
  251. assert item4['TrueBoolean'] == True
  252. assert item4['FalseBoolean'] == False
  253. # The values will not necessarily be in the same order as when
  254. # we wrote them to the DB.
  255. for i in item4['IntSetAttr']:
  256. assert i in integer_set
  257. for i in item4['FloatSetAttr']:
  258. assert i in float_set
  259. for i in item4['MixedSetAttr']:
  260. assert i in mixed_set
  261. for i in item4['StrSetAttr']:
  262. assert i in str_set
  263. # Try a batch get
  264. batch_list = c.new_batch_list()
  265. batch_list.add_batch(table, [(item2_key, item2_range),
  266. (item3_key, item3_range)])
  267. response = batch_list.submit()
  268. assert len(response['Responses'][table.name]['Items']) == 2
  269. # Try a few batch write operations
  270. item4_key = 'Amazon S3'
  271. item4_range = 'S3 Thread 2'
  272. item4_attrs = {
  273. 'Message': 'S3 Thread 2 message text',
  274. 'LastPostedBy': 'User A',
  275. 'Views': 0,
  276. 'Replies': 0,
  277. 'Answered': 0,
  278. 'Tags': set(['largeobject', 'multipart upload']),
  279. 'LastPostDateTime': '12/9/2011 11:36:03 PM'
  280. }
  281. item5_key = 'Amazon S3'
  282. item5_range = 'S3 Thread 3'
  283. item5_attrs = {
  284. 'Message': 'S3 Thread 3 message text',
  285. 'LastPostedBy': 'User A',
  286. 'Views': 0,
  287. 'Replies': 0,
  288. 'Answered': 0,
  289. 'Tags': set(['largeobject', 'multipart upload']),
  290. 'LastPostDateTime': '12/9/2011 11:36:03 PM'
  291. }
  292. item4 = table.new_item(item4_key, item4_range, item4_attrs)
  293. item5 = table.new_item(item5_key, item5_range, item5_attrs)
  294. batch_list = c.new_batch_write_list()
  295. batch_list.add_batch(table, puts=[item4, item5])
  296. response = batch_list.submit()
  297. # should really check for unprocessed items
  298. batch_list = c.new_batch_write_list()
  299. batch_list.add_batch(table, deletes=[(item4_key, item4_range),
  300. (item5_key, item5_range)])
  301. response = batch_list.submit()
  302. # Try queries
  303. results = table.query('Amazon DynamoDB', BEGINS_WITH('DynamoDB'))
  304. n = 0
  305. for item in results:
  306. n += 1
  307. assert n == 2
  308. # Try scans
  309. results = table.scan({'Tags': CONTAINS('table')})
  310. n = 0
  311. for item in results:
  312. n += 1
  313. assert n == 2
  314. # Try to delete the item with the right Expected value
  315. expected = {'Views': 0}
  316. item1.delete(expected_value=expected)
  317. self.assertFalse(table.has_item(item1_key, range_key=item1_range,
  318. consistent_read=True))
  319. # Now delete the remaining items
  320. ret_vals = item2.delete(return_values='ALL_OLD')
  321. # some additional checks here would be useful
  322. assert ret_vals['Attributes'][hash_key_name] == item2_key
  323. assert ret_vals['Attributes'][range_key_name] == item2_range
  324. item3.delete()
  325. table2_item1.delete()
  326. # Now delete the tables
  327. table.delete()
  328. table2.delete()
  329. assert table.status == 'DELETING'
  330. assert table2.status == 'DELETING'
  331. print '--- tests completed ---'