Repository: localstack/localstack

Issue:
'DYNAMODB_ERROR_PROBABILITY' configuration throwing thread._local errors
<!-- Love localstack? Please consider supporting our collective:
👉  https://opencollective.com/localstack/donate -->
With `DYNAMODB_ERROR_PROBABILITY=0.9` in my docker-compose, I am seeing errors thrown up when I try to put items using the Document Client, expecting to get ThroughputExceeded exceptions.  Localstack throws about 10 of these:
```
localstack_1  | 2018-07-11T21:19:27:ERROR:localstack.services.generic_proxy: Error forwarding request: 'thread._local' object has no attribute 'existing_item' Traceback (most recent call last):
localstack_1  |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 215, in forward
localstack_1  |     updated_response = self.proxy.update_listener.return_response(**kwargs)
localstack_1  |   File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 145, in return_response
localstack_1  |     existing_item = ProxyListenerDynamoDB.thread_local.existing_item
localstack_1  | AttributeError: 'thread._local' object has no attribute 'existing_item'
```
And randomly in the middle this pops up:
```
localstack_1  | 2018-07-11T21:19:27:ERROR:localstack.services.generic_proxy: Error forwarding request: An error occurred (ProvisionedThroughputExceededException) when calling the DescribeTable operation (reached max retries: 9): The level of configured provisioned throughput for the table was exceeded. Consider increasing your provisioning level with the UpdateTable API Traceback (most recent call last):
localstack_1  |   File "/opt/code/localstack/localstack/services/generic_proxy.py", line 181, in forward
localstack_1  |     path=path, data=data, headers=forward_headers)
localstack_1  |   File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 44, in forward_request
localstack_1  |     ProxyListenerDynamoDB.thread_local.existing_item = find_existing_item(data)
localstack_1  |   File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 208, in find_existing_item
localstack_1  |     schema = ddb_client.describe_table(TableName=table_name)
localstack_1  |   File "/opt/code/localstack/.venv/lib/python2.7/site-packages/botocore/client.py", line 314, in _api_call
localstack_1  |     return self._make_api_call(operation_name, kwargs)
localstack_1  |   File "/opt/code/localstack/.venv/lib/python2.7/site-packages/botocore/client.py", line 612, in _make_api_call
localstack_1  |     raise error_class(parsed_response, operation_name)
localstack_1  | ProvisionedThroughputExceededException: An error occurred (ProvisionedThroughputExceededException) when calling the DescribeTable operation (reached max retries: 9): The level of configured provisioned throughput for the table was exceeded. Consider increasing your provisioning level with the UpdateTable API
localstack_1  |
```
I double-checked that the table I'm aimed at exists.  Here's a basic Node example of what I ran to see the errors:
```
"use strict";
const AWS = require("aws-sdk");

const endpoint = `http://localhost:4569`;
const localConfig = {
    accessKeyId: "123",
    secretAccessKey: "abc",
    region: "us-west-2",
    sslEnabled: false,
    endpoint: endpoint
};
const docClient = new AWS.DynamoDB.DocumentClient(localConfig);

function getFormattedDate() {
    // use ISO-8601
    return new Date().toISOString();
}

function writeScanToDB(sensorList) {
    let tableName = "SomeTable";

    let params = {
        TableName: tableName,
        Item: {
            date: getFormattedDate(),
            ListAttribute: sensorList
        }
    };

    docClient
        .put(params)
        .promise()
        .catch(function(err) {
            console.log("Unable to write scan to DB:\n", err);
        });
}

let sensorList = [
    {
        mac: "AB:CD"
    },
    {
        mac: "AB:ED"
    }
];
writeScanToDB(sensorList);

```
Let me know if any other information would be helpful.


Discussion:

Resolving patch:
diff --git a/localstack/services/dynamodb/dynamodb_listener.py b/localstack/services/dynamodb/dynamodb_listener.py
--- a/localstack/services/dynamodb/dynamodb_listener.py
+++ b/localstack/services/dynamodb/dynamodb_listener.py
@@ -22,6 +22,12 @@
 # set up logger
 LOGGER = logging.getLogger(__name__)
 
+# list of actions subject to throughput limitations
+THROTTLED_ACTIONS = [
+    'PutItem', 'BatchWriteItem', 'UpdateItem', 'DeleteItem', 'TransactWriteItems',
+    'GetItem', 'Query', 'Scan', 'TransactGetItems', 'BatchGetItem'
+]
+
 
 class ProxyListenerDynamoDB(ProxyListener):
     thread_local = threading.local()
@@ -37,13 +43,15 @@ def forward_request(self, method, path, data, headers):
 
         data = json.loads(to_str(data))
         ddb_client = aws_stack.connect_to_service('dynamodb')
+        action = headers.get('X-Amz-Target')
 
         if random.random() < config.DYNAMODB_ERROR_PROBABILITY:
-            return error_response_throughput()
+            throttled = ['%s.%s' % (ACTION_PREFIX, a) for a in THROTTLED_ACTIONS]
+            if action in throttled:
+                return error_response_throughput()
 
         ProxyListenerDynamoDB.thread_local.existing_item = None
 
-        action = headers.get('X-Amz-Target')
         if action == '%s.CreateTable' % ACTION_PREFIX:
             # Check if table exists, to avoid error log output from DynamoDBLocal
             table_names = ddb_client.list_tables()['TableNames']


Test patch: