Repository: localstack/localstack

Issue:
bug: Dynamodb: Error: error creating DynamoDB Table: ResourceInUseException: Table already created
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

I'm trying to create dynamodb table using terraform using latest localstack image:

Terraform output:

```➜  terraform-aws-dynamodb-example git:(master) ✗ terraform apply
var.region
  The AWS region to deploy to

  Enter a value: us-east-1


Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_dynamodb_table.example will be created
  + resource "aws_dynamodb_table" "example" {
      + arn              = (known after apply)
      + billing_mode     = "PAY_PER_REQUEST"
      + hash_key         = "userId"
      + id               = (known after apply)
      + name             = "terratest-example"
      + range_key        = "department"
      + stream_arn       = (known after apply)
      + stream_label     = (known after apply)
      + stream_view_type = (known after apply)
      + tags             = {
          + "Environment" = "production"
        }
      + tags_all         = {
          + "Environment" = "production"
        }

      + attribute {
          + name = "department"
          + type = "S"
        }
      + attribute {
          + name = "userId"
          + type = "S"
        }

      + point_in_time_recovery {
          + enabled = (known after apply)
        }

      + server_side_encryption {
          + enabled     = true
          + kms_key_arn = (known after apply)
        }

      + ttl {
          + attribute_name = "expires"
          + enabled        = true
          + kms_key_arn    = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_dynamodb_table.example: Creating...
╷
│ Error: error creating DynamoDB Table: ResourceInUseException: Table already created
│
│   with aws_dynamodb_table.example,
│   on main.tf line 51, in resource "aws_dynamodb_table" "example":
│   51: resource "aws_dynamodb_table" "example" {
│


**Localstack container output:**

```2021-10-08T23:29:53:INFO:localstack.utils.run: Thread run method <function AdaptiveThreadPool.submit.<locals>._run at 0x7feba6300b90>(None) failed: 'SSEType' Traceback (most recent call last):
  File "/opt/code/localstack/localstack/utils/run.py", line 150, in run
    result = self.func(self.params, **kwargs)
  File "/opt/code/localstack/localstack/utils/async_utils.py", line 31, in _run
    return fn(*args, **kwargs)
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 538, in handler
    server_address=parsed_url.netloc,
  File "/opt/code/localstack/localstack/utils/aws/request_context.py", line 193, in modify_and_forward
    method, path, data_bytes=data_bytes, headers=headers, *args, **kwargs
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 323, in modify_and_forward
    method=method, path=path, data=data, headers=headers
  File "/opt/code/localstack/localstack/services/edge.py", line 170, in forward_request
    result = do_forward_request(api, method, path, data, headers, port=port)
  File "/opt/code/localstack/localstack/services/edge.py", line 212, in do_forward_request
    result = do_forward_request_inmem(api, method, path, data, headers, port=port)
  File "/opt/code/localstack/localstack/services/edge.py", line 244, in do_forward_request_inmem
    server_address=server_address,
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 398, in modify_and_forward
    updated_response = update_listener.return_response(**kwargs)
  File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 483, in return_response
    table_definitions["SSEDescription"] = get_sse_description(sse_specification)
  File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 790, in get_sse_description
    "SSEType": data["SSEType"] if data["Enabled"] else None,
KeyError: 'SSEType'

2021-10-08T23:29:53:WARNING:localstack.utils.server.http2_server: Error in proxy handler for request POST http://localhost:4566/: 'SSEType' Traceback (most recent call last):
  File "/opt/code/localstack/localstack/utils/server/http2_server.py", line 130, in index
    result = await run_sync(handler, request, data)
  File "/opt/code/localstack/localstack/utils/async_utils.py", line 86, in run_sync
    return await loop.run_in_executor(thread_pool, copy_context().run, func_wrapped)
  File "/opt/code/localstack/localstack/utils/run.py", line 150, in run
    result = self.func(self.params, **kwargs)
  File "/opt/code/localstack/localstack/utils/async_utils.py", line 31, in _run
    return fn(*args, **kwargs)
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 538, in handler
    server_address=parsed_url.netloc,
  File "/opt/code/localstack/localstack/utils/aws/request_context.py", line 193, in modify_and_forward
    method, path, data_bytes=data_bytes, headers=headers, *args, **kwargs
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 323, in modify_and_forward
    method=method, path=path, data=data, headers=headers
  File "/opt/code/localstack/localstack/services/edge.py", line 170, in forward_request
    result = do_forward_request(api, method, path, data, headers, port=port)
  File "/opt/code/localstack/localstack/services/edge.py", line 212, in do_forward_request
    result = do_forward_request_inmem(api, method, path, data, headers, port=port)
  File "/opt/code/localstack/localstack/services/edge.py", line 244, in do_forward_request_inmem
    server_address=server_address,
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 398, in modify_and_forward
    updated_response = update_listener.return_response(**kwargs)
  File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 483, in return_response
    table_definitions["SSEDescription"] = get_sse_description(sse_specification)
  File "/opt/code/localstack/localstack/services/dynamodb/dynamodb_listener.py", line 790, in get_sse_description
    "SSEType": data["SSEType"] if data["Enabled"] else None,
KeyError: 'SSEType'

2021-10-08T23:29:53:ERROR:asyncio: Exception in callback _chain_future.<locals>._set_state(<Future finis...or('SSEType')>, <Future at 0x...ised KeyError>) at /usr/lib/python3.7/asyncio/futures.py:345
handle: <Handle _chain_future.<locals>._set_state(<Future finis...or('SSEType')>, <Future at 0x...ised KeyError>) at /usr/lib/python3.7/asyncio/futures.py:345>
Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/lib/python3.7/asyncio/futures.py", line 347, in _set_state
    _copy_future_state(other, future)
  File "/usr/lib/python3.7/asyncio/futures.py", line 317, in _copy_future_state
    assert not dest.done()
AssertionError```



### Expected Behavior

Terraform output should looks like:

```✗ terraform apply
var.region
  The AWS region to deploy to

  Enter a value: us-east-1


Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_dynamodb_table.example will be created
  + resource "aws_dynamodb_table" "example" {
      + arn              = (known after apply)
      + billing_mode     = "PAY_PER_REQUEST"
      + hash_key         = "userId"
      + id               = (known after apply)
      + name             = "terratest-example"
      + range_key        = "department"
      + stream_arn       = (known after apply)
      + stream_label     = (known after apply)
      + stream_view_type = (known after apply)
      + tags             = {
          + "Environment" = "production"
        }
      + tags_all         = {
          + "Environment" = "production"
        }

      + attribute {
          + name = "department"
          + type = "S"
        }
      + attribute {
          + name = "userId"
          + type = "S"
        }

      + point_in_time_recovery {
          + enabled = (known after apply)
        }

      + server_side_encryption {
          + enabled     = true
          + kms_key_arn = (known after apply)
        }

      + ttl {
          + attribute_name = "expires"
          + enabled        = true
          + kms_key_arn    = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_dynamodb_table.example: Creating...
aws_dynamodb_table.example: Creation complete after 0s [id=terratest-example]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```



### How are you starting LocalStack?

With a `docker run` command

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

    docker run --rm -it -p 4566:4566 -p 4571:4571 localstack/localstack

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

Create file: main.tf

```
provider` "aws" {
  region                      = "us-east-1"
  s3_force_path_style         = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    apigateway     = "http://localhost:4566"
    cloudformation = "http://localhost:4566"
    cloudwatch     = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    es             = "http://localhost:4566"
    firehose       = "http://localhost:4566"
    iam            = "http://localhost:4566"
    kinesis        = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    route53        = "http://localhost:4566"
    redshift       = "http://localhost:4566"
    s3             = "http://localhost:4566"
    secretsmanager = "http://localhost:4566"
    ses            = "http://localhost:4566"
    sns            = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    ssm            = "http://localhost:4566"
    stepfunctions  = "http://localhost:4566"
    sts            = "http://localhost:4566"
    ec2            = "http://localhost:4566"
  }
}

terraform {
  # This module is now only being tested with Terraform 0.13.x. However, to make upgrading easier, we are setting
  # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it
  # forwards compatible with 0.13.x code.
  required_version = ">= 0.12.26"
}

# ---------------------------------------------------------------------------------------------------------------------
# CREATE THE DYNAMODB TABLE
# ---------------------------------------------------------------------------------------------------------------------

resource "aws_dynamodb_table" "example" {
  name         = var.table_name
  hash_key     = "userId"
  range_key    = "department"
  billing_mode = "PAY_PER_REQUEST"

  server_side_encryption {
    enabled = true
  }
  # point_in_time_recovery {
  #   enabled = true
  # }

  attribute {
    name = "userId"
    type = "S"
  }
  attribute {
    name = "department"
    type = "S"
  }

  ttl {
    enabled        = true
    attribute_name = "expires"
  }

  tags = {
    Environment = "production"
  }
}

variable "table_name" {
  description = "The name to set for the dynamoDB table."
  type        = string
  default     = "terratest-example"
}
```

Run terraform from the main.tf directory:

terraform init
terraform plan
terraform apply

### Environment

```markdown
- OS: macOS Big Sur
- LocalStack: latest/0.12.18
```


### Anything else?

This bug is not there in localstack image 0.12.17, it works fine with this.



Discussion:
@mgagliardo, looks like this issue started after we fixed the https://github.com/localstack/localstack/issues/4581


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
@@ -54,6 +54,8 @@
 ]
 THROTTLED_ACTIONS = READ_THROTTLED_ACTIONS + WRITE_THROTTLED_ACTIONS
 
+MANAGED_KMS_KEYS = {}
+
 
 class DynamoDBRegion(RegionBackend):
     # maps global table names to configurations
@@ -784,14 +786,33 @@ def _thread_local(name, default=None):
             return default
 
 
+def get_sse_kms_managed_key():
+    if MANAGED_KMS_KEYS.get(aws_stack.get_region()):
+        return MANAGED_KMS_KEYS[aws_stack.get_region()]
+    kms_client = aws_stack.connect_to_service("kms")
+    key_data = kms_client.create_key(Description="Default key that protects DynamoDB data")
+    key_id = key_data["KeyMetadata"]["KeyId"]
+    # not really happy with this import here
+    from localstack.services.kms import kms_listener
+
+    kms_listener.set_key_managed(key_id)
+    MANAGED_KMS_KEYS[aws_stack.get_region()] = key_id
+    return key_id
+
+
 def get_sse_description(data):
-    return {
-        "Status": "ENABLED" if data["Enabled"] else "UPDATING",
-        "SSEType": data["SSEType"] if data["Enabled"] else None,
-        "KMSMasterKeyArn": aws_stack.kms_key_arn(data["KMSMasterKeyId"])
-        if data["Enabled"]
-        else None,
-    }
+    if data.get("Enabled"):
+        kms_master_key_id = data.get("KMSMasterKeyId")
+        if not kms_master_key_id:
+            # this is of course not the actual key for dynamodb, just a better, since existing, mock
+            kms_master_key_id = get_sse_kms_managed_key()
+        kms_master_key_id = aws_stack.kms_key_arn(kms_master_key_id)
+        return {
+            "Status": "ENABLED",
+            "SSEType": "KMS",  # no other value is allowed here
+            "KMSMasterKeyArn": kms_master_key_id,
+        }
+    return {}
 
 
 def handle_special_request(method, path, data, headers):
diff --git a/localstack/services/kms/kms_listener.py b/localstack/services/kms/kms_listener.py
--- a/localstack/services/kms/kms_listener.py
+++ b/localstack/services/kms/kms_listener.py
@@ -271,6 +271,18 @@ def _get_key_pairs():
     return key_pairs
 
 
+def set_key_managed(key_id) -> None:
+    """
+    Sets a KMS key to AWS managed
+    :param key_id: ID of the KMS key
+    """
+    region_name = aws_stack.get_region()
+    backend = kms_backends.get(region_name)
+    key_data = backend.keys.get(key_id)
+    if key_data:
+        key_data.key_manager = "AWS"
+
+
 class ProxyListenerKMS(ProxyListener):
     def forward_request(self, method, path, data, headers):
         action = headers.get("X-Amz-Target") or ""


Test patch:
diff --git a/tests/integration/test_dynamodb.py b/tests/integration/test_dynamodb.py
--- a/tests/integration/test_dynamodb.py
+++ b/tests/integration/test_dynamodb.py
@@ -852,6 +852,32 @@ def test_dynamodb_create_table_with_sse_specification(self):
 
         delete_table(table_name)
 
+    def test_dynamodb_create_table_with_partial_sse_specification(self):
+        dynamodb = aws_stack.connect_to_service("dynamodb")
+        table_name = "ddb-table-%s" % short_uid()
+
+        sse_specification = {"Enabled": True}
+
+        result = dynamodb.create_table(
+            TableName=table_name,
+            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
+            ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
+            SSESpecification=sse_specification,
+            Tags=TEST_DDB_TAGS,
+        )
+
+        self.assertTrue(result["TableDescription"]["SSEDescription"])
+        self.assertEqual("ENABLED", result["TableDescription"]["SSEDescription"]["Status"])
+        self.assertEqual("KMS", result["TableDescription"]["SSEDescription"]["SSEType"])
+        self.assertIn("KMSMasterKeyArn", result["TableDescription"]["SSEDescription"])
+        kms_master_key_arn = result["TableDescription"]["SSEDescription"]["KMSMasterKeyArn"]
+        kms_client = aws_stack.connect_to_service("kms")
+        result = kms_client.describe_key(KeyId=kms_master_key_arn)
+        self.assertEqual("AWS", result["KeyMetadata"]["KeyManager"])
+
+        delete_table(table_name)
+
 
 def delete_table(name):
     dynamodb_client = aws_stack.connect_to_service("dynamodb")