PageRenderTime 46ms CodeModel.GetById 23ms app.highlight 19ms RepoModel.GetById 1ms 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"""
 24Tests for Layer2 of Amazon DynamoDB
 25"""
 26
 27import unittest
 28import time
 29import uuid
 30from boto.dynamodb.exceptions import DynamoDBKeyNotFoundError, DynamoDBItemError
 31from boto.dynamodb.exceptions import DynamoDBConditionalCheckFailedError
 32from boto.dynamodb.layer2 import Layer2
 33from boto.dynamodb.types import get_dynamodb_type
 34from boto.dynamodb.condition import *
 35
 36class DynamoDBLayer2Test (unittest.TestCase):
 37    dynamodb = True
 38
 39    def test_layer2_basic(self):
 40        print '--- running Amazon DynamoDB Layer2 tests ---'
 41        c = Layer2()
 42
 43        # First create a schema for the table
 44        hash_key_name = 'forum_name'
 45        hash_key_proto_value = ''
 46        range_key_name = 'subject'
 47        range_key_proto_value = ''
 48        schema = c.create_schema(hash_key_name, hash_key_proto_value,
 49                                 range_key_name, range_key_proto_value)
 50
 51        # Create another schema without a range key
 52        schema2 = c.create_schema('post_id', '')
 53
 54        # Now create a table
 55        index = int(time.time())
 56        table_name = 'test-%d' % index
 57        read_units = 5
 58        write_units = 5
 59        table = c.create_table(table_name, schema, read_units, write_units)
 60        assert table.name == table_name
 61        assert table.schema.hash_key_name == hash_key_name
 62        assert table.schema.hash_key_type == get_dynamodb_type(hash_key_proto_value)
 63        assert table.schema.range_key_name == range_key_name
 64        assert table.schema.range_key_type == get_dynamodb_type(range_key_proto_value)
 65        assert table.read_units == read_units
 66        assert table.write_units == write_units
 67        assert table.item_count == 0
 68        assert table.size_bytes == 0
 69
 70        # Create the second table
 71        table2_name = 'test-%d' % (index + 1)
 72        table2 = c.create_table(table2_name, schema2, read_units, write_units)
 73
 74        # Wait for table to become active
 75        table.refresh(wait_for_active=True)
 76        table2.refresh(wait_for_active=True)
 77
 78        # List tables and make sure new one is there
 79        table_names = c.list_tables()
 80        assert table_name in table_names
 81        assert table2_name in table_names
 82
 83        # Update the tables ProvisionedThroughput
 84        new_read_units = 10
 85        new_write_units = 5
 86        table.update_throughput(new_read_units, new_write_units)
 87
 88        # Wait for table to be updated
 89        table.refresh(wait_for_active=True)
 90        assert table.read_units == new_read_units
 91        assert table.write_units == new_write_units
 92
 93        # Put an item
 94        item1_key = 'Amazon DynamoDB'
 95        item1_range = 'DynamoDB Thread 1'
 96        item1_attrs = {
 97            'Message': 'DynamoDB thread 1 message text',
 98            'LastPostedBy': 'User A',
 99            'Views': 0,
100            'Replies': 0,
101            'Answered': 0,
102            'Public': True,
103            'Tags': set(['index', 'primarykey', 'table']),
104            'LastPostDateTime':  '12/9/2011 11:36:03 PM'}
105
106        # Test a few corner cases with new_item
107        
108        # Try supplying a hash_key as an arg and as an item in attrs
109        item1_attrs[hash_key_name] = 'foo'
110        foobar_item = table.new_item(item1_key, item1_range, item1_attrs)
111        assert foobar_item.hash_key == item1_key
112
113        # Try supplying a range_key as an arg and as an item in attrs
114        item1_attrs[range_key_name] = 'bar'
115        foobar_item = table.new_item(item1_key, item1_range, item1_attrs)
116        assert foobar_item.range_key == item1_range
117
118        # Try supplying hash and range key in attrs dict
119        foobar_item = table.new_item(attrs=item1_attrs)
120        assert foobar_item.hash_key == 'foo'
121        assert foobar_item.range_key == 'bar'
122
123        del item1_attrs[hash_key_name]
124        del item1_attrs[range_key_name]
125
126        item1 = table.new_item(item1_key, item1_range, item1_attrs)
127        # make sure the put() succeeds
128        try:
129            item1.put()
130        except c.layer1.ResponseError, e:
131            raise Exception("Item put failed: %s" % e)
132
133        # Try to get an item that does not exist.
134        self.assertRaises(DynamoDBKeyNotFoundError,
135                          table.get_item, 'bogus_key', item1_range)
136
137        # Now do a consistent read and check results
138        item1_copy = table.get_item(item1_key, item1_range,
139                                    consistent_read=True)
140        assert item1_copy.hash_key == item1.hash_key
141        assert item1_copy.range_key == item1.range_key
142        for attr_name in item1_copy:
143            val = item1_copy[attr_name]
144            if isinstance(val, (int, long, float, basestring)):
145                assert val == item1[attr_name]
146
147        # Try retrieving only select attributes
148        attributes = ['Message', 'Views']
149        item1_small = table.get_item(item1_key, item1_range,
150                                     attributes_to_get=attributes,
151                                     consistent_read=True)
152        for attr_name in item1_small:
153            # The item will include the attributes we asked for as
154            # well as the hashkey and rangekey, so filter those out.
155            if attr_name not in (item1_small.hash_key_name,
156                                 item1_small.range_key_name):
157                assert attr_name in attributes
158
159        self.assertTrue(table.has_item(item1_key, range_key=item1_range,
160                                       consistent_read=True))
161
162        # Try to delete the item with the wrong Expected value
163        expected = {'Views': 1}
164        self.assertRaises(DynamoDBConditionalCheckFailedError,
165                          item1.delete, expected_value=expected)
166
167        # Try to delete a value while expecting a non-existant attribute
168        expected = {'FooBar': True}
169        try:
170            item1.delete(expected_value=expected)
171        except c.layer1.ResponseError, e:
172            pass
173
174        # Now update the existing object
175        item1.add_attribute('Replies', 2)
176
177        removed_attr = 'Public'
178        item1.delete_attribute(removed_attr)
179
180        removed_tag = item1_attrs['Tags'].copy().pop()
181        item1.delete_attribute('Tags', set([removed_tag]))
182
183        replies_by_set = set(['Adam', 'Arnie'])
184        item1.put_attribute('RepliesBy', replies_by_set)
185        retvals = item1.save(return_values='ALL_OLD')
186        # Need more tests here for variations on return_values
187        assert 'Attributes' in retvals
188
189        # Check for correct updates
190        item1_updated = table.get_item(item1_key, item1_range,
191                                       consistent_read=True)
192        assert item1_updated['Replies'] == item1_attrs['Replies'] + 2
193        self.assertFalse(removed_attr in item1_updated)
194        self.assertTrue(removed_tag not in item1_updated['Tags'])
195        self.assertTrue('RepliesBy' in item1_updated)
196        self.assertTrue(item1_updated['RepliesBy'] == replies_by_set)
197
198        # Put a few more items into the table
199        item2_key = 'Amazon DynamoDB'
200        item2_range = 'DynamoDB Thread 2'
201        item2_attrs = {
202            'Message': 'DynamoDB thread 2 message text',
203            'LastPostedBy': 'User A',
204            'Views': 0,
205            'Replies': 0,
206            'Answered': 0,
207            'Tags': set(["index", "primarykey", "table"]),
208            'LastPost2DateTime':  '12/9/2011 11:36:03 PM'}
209        item2 = table.new_item(item2_key, item2_range, item2_attrs)
210        item2.put()
211
212        item3_key = 'Amazon S3'
213        item3_range = 'S3 Thread 1'
214        item3_attrs = {
215            'Message': 'S3 Thread 1 message text',
216            'LastPostedBy': 'User A',
217            'Views': 0,
218            'Replies': 0,
219            'Answered': 0,
220            'Tags': set(['largeobject', 'multipart upload']),
221            'LastPostDateTime': '12/9/2011 11:36:03 PM'
222            }
223        item3 = table.new_item(item3_key, item3_range, item3_attrs)
224        item3.put()
225
226        # Put an item into the second table
227        table2_item1_key = uuid.uuid4().hex
228        table2_item1_attrs = {
229            'DateTimePosted': '25/1/2011 12:34:56 PM',
230            'Text': 'I think boto rocks and so does DynamoDB'
231            }
232        table2_item1 = table2.new_item(table2_item1_key,
233                                       attrs=table2_item1_attrs)
234        table2_item1.put()
235
236        # Try a few queries
237        items = table.query('Amazon DynamoDB', BEGINS_WITH('DynamoDB'))
238        n = 0
239        for item in items:
240            n += 1
241        assert n == 2
242        assert items.consumed_units > 0
243
244        items = table.query('Amazon DynamoDB', BEGINS_WITH('DynamoDB'),
245                            request_limit=1, max_results=1)
246        n = 0
247        for item in items:
248            n += 1
249        assert n == 1
250        assert items.consumed_units > 0
251
252        # Try a few scans
253        items = table.scan()
254        n = 0
255        for item in items:
256            n += 1
257        assert n == 3
258        assert items.consumed_units > 0
259
260        items = table.scan({'Replies': GT(0)})
261        n = 0
262        for item in items:
263            n += 1
264        assert n == 1
265        assert items.consumed_units > 0
266
267        # Test some integer and float attributes
268        integer_value = 42
269        float_value = 345.678
270        item3['IntAttr'] = integer_value
271        item3['FloatAttr'] = float_value
272
273        # Test booleans
274        item3['TrueBoolean'] = True
275        item3['FalseBoolean'] = False
276
277        # Test some set values
278        integer_set = set([1, 2, 3, 4, 5])
279        float_set = set([1.1, 2.2, 3.3, 4.4, 5.5])
280        mixed_set = set([1, 2, 3.3, 4, 5.555])
281        str_set = set(['foo', 'bar', 'fie', 'baz'])
282        item3['IntSetAttr'] = integer_set
283        item3['FloatSetAttr'] = float_set
284        item3['MixedSetAttr'] = mixed_set
285        item3['StrSetAttr'] = str_set
286        item3.put()
287
288        # Now do a consistent read
289        item4 = table.get_item(item3_key, item3_range, consistent_read=True)
290        assert item4['IntAttr'] == integer_value
291        assert item4['FloatAttr'] == float_value
292        assert item4['TrueBoolean'] == True
293        assert item4['FalseBoolean'] == False
294        # The values will not necessarily be in the same order as when
295        # we wrote them to the DB.
296        for i in item4['IntSetAttr']:
297            assert i in integer_set
298        for i in item4['FloatSetAttr']:
299            assert i in float_set
300        for i in item4['MixedSetAttr']:
301            assert i in mixed_set
302        for i in item4['StrSetAttr']:
303            assert i in str_set
304
305        # Try a batch get
306        batch_list = c.new_batch_list()
307        batch_list.add_batch(table, [(item2_key, item2_range),
308                                     (item3_key, item3_range)])
309        response = batch_list.submit()
310        assert len(response['Responses'][table.name]['Items']) == 2
311
312        # Try a few batch write operations
313        item4_key = 'Amazon S3'
314        item4_range = 'S3 Thread 2'
315        item4_attrs = {
316            'Message': 'S3 Thread 2 message text',
317            'LastPostedBy': 'User A',
318            'Views': 0,
319            'Replies': 0,
320            'Answered': 0,
321            'Tags': set(['largeobject', 'multipart upload']),
322            'LastPostDateTime': '12/9/2011 11:36:03 PM'
323            }
324        item5_key = 'Amazon S3'
325        item5_range = 'S3 Thread 3'
326        item5_attrs = {
327            'Message': 'S3 Thread 3 message text',
328            'LastPostedBy': 'User A',
329            'Views': 0,
330            'Replies': 0,
331            'Answered': 0,
332            'Tags': set(['largeobject', 'multipart upload']),
333            'LastPostDateTime': '12/9/2011 11:36:03 PM'
334            }
335        item4 = table.new_item(item4_key, item4_range, item4_attrs)
336        item5 = table.new_item(item5_key, item5_range, item5_attrs)
337        batch_list = c.new_batch_write_list()
338        batch_list.add_batch(table, puts=[item4, item5])
339        response = batch_list.submit()
340        # should really check for unprocessed items
341
342        batch_list = c.new_batch_write_list()
343        batch_list.add_batch(table, deletes=[(item4_key, item4_range),
344                                             (item5_key, item5_range)])
345        response = batch_list.submit()
346        
347
348        # Try queries
349        results = table.query('Amazon DynamoDB', BEGINS_WITH('DynamoDB'))
350        n = 0
351        for item in results:
352            n += 1
353        assert n == 2
354        
355        # Try scans
356        results = table.scan({'Tags': CONTAINS('table')})
357        n = 0
358        for item in results:
359            n += 1
360        assert n == 2
361
362        # Try to delete the item with the right Expected value
363        expected = {'Views': 0}
364        item1.delete(expected_value=expected)
365
366        self.assertFalse(table.has_item(item1_key, range_key=item1_range,
367                                       consistent_read=True))
368        # Now delete the remaining items
369        ret_vals = item2.delete(return_values='ALL_OLD')
370        # some additional checks here would be useful
371        assert ret_vals['Attributes'][hash_key_name] == item2_key
372        assert ret_vals['Attributes'][range_key_name] == item2_range
373        
374        item3.delete()
375        table2_item1.delete()
376
377        # Now delete the tables
378        table.delete()
379        table2.delete()
380        assert table.status == 'DELETING'
381        assert table2.status == 'DELETING'
382
383        print '--- tests completed ---'