moto/tests/test_dynamodb/test_dynamodb_create_table.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

494 lines
18 KiB
Python
Raw Normal View History

2022-02-10 20:09:45 +00:00
import boto3
from botocore.exceptions import ClientError
from datetime import datetime
import pytest
2023-11-04 10:37:32 +00:00
from moto import mock_dynamodb
2022-08-13 09:49:43 +00:00
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
2023-11-04 10:37:32 +00:00
from . import dynamodb_aws_verified
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_standard():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="messages",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5},
)
actual = client.describe_table(TableName="messages")["Table"]
assert actual["AttributeDefinitions"] == [
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
]
assert isinstance(actual["CreationDateTime"], datetime)
assert actual["GlobalSecondaryIndexes"] == []
assert actual["LocalSecondaryIndexes"] == []
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 5,
}
assert actual["TableSizeBytes"] == 0
assert actual["TableName"] == "messages"
assert actual["TableStatus"] == "ACTIVE"
assert (
actual["TableArn"] == f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
2022-02-10 20:09:45 +00:00
)
assert actual["KeySchema"] == [
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
]
assert actual["ItemCount"] == 0
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_local_index():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="messages",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
{"AttributeName": "threads", "AttributeType": "S"},
],
LocalSecondaryIndexes=[
{
"IndexName": "threads_index",
"KeySchema": [
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "threads", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "ALL"},
}
],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5},
)
actual = client.describe_table(TableName="messages")["Table"]
assert actual["AttributeDefinitions"] == [
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
{"AttributeName": "threads", "AttributeType": "S"},
]
assert isinstance(actual["CreationDateTime"], datetime)
assert actual["GlobalSecondaryIndexes"] == []
assert actual["LocalSecondaryIndexes"] == [
{
"IndexName": "threads_index",
"KeySchema": [
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "threads", "KeyType": "RANGE"},
],
"Projection": {"ProjectionType": "ALL"},
}
]
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 5,
}
assert actual["TableSizeBytes"] == 0
assert actual["TableName"] == "messages"
assert actual["TableStatus"] == "ACTIVE"
assert (
actual["TableArn"] == f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
2022-02-10 20:09:45 +00:00
)
assert actual["KeySchema"] == [
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
]
assert actual["ItemCount"] == 0
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_gsi():
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
table = dynamodb.create_table(
TableName="users",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
GlobalSecondaryIndexes=[
{
"IndexName": "test_gsi",
"KeySchema": [{"AttributeName": "subject", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
}
],
)
assert table["TableDescription"]["GlobalSecondaryIndexes"] == [
{
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
"IndexName": "test_gsi",
"Projection": {"ProjectionType": "ALL"},
"IndexStatus": "ACTIVE",
"ProvisionedThroughput": {
"ReadCapacityUnits": 0,
"WriteCapacityUnits": 0,
},
}
]
2022-02-10 20:09:45 +00:00
table = dynamodb.create_table(
TableName="users2",
KeySchema=[
{"AttributeName": "forum_name", "KeyType": "HASH"},
{"AttributeName": "subject", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "forum_name", "AttributeType": "S"},
{"AttributeName": "subject", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
GlobalSecondaryIndexes=[
{
"IndexName": "test_gsi",
"KeySchema": [{"AttributeName": "subject", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 3,
"WriteCapacityUnits": 5,
},
}
],
)
assert table["TableDescription"]["GlobalSecondaryIndexes"] == [
{
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
"IndexName": "test_gsi",
"Projection": {"ProjectionType": "ALL"},
"IndexStatus": "ACTIVE",
"ProvisionedThroughput": {
"ReadCapacityUnits": 3,
"WriteCapacityUnits": 5,
},
}
]
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_stream_specification():
conn = boto3.client("dynamodb", region_name="us-east-1")
resp = conn.create_table(
TableName="test-streams",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
StreamSpecification={
"StreamEnabled": True,
"StreamViewType": "NEW_AND_OLD_IMAGES",
},
)
assert resp["TableDescription"]["StreamSpecification"] == {
"StreamEnabled": True,
"StreamViewType": "NEW_AND_OLD_IMAGES",
}
assert "LatestStreamLabel" in resp["TableDescription"]
assert "LatestStreamArn" in resp["TableDescription"]
2022-02-10 20:09:45 +00:00
resp = conn.delete_table(TableName="test-streams")
assert "StreamSpecification" in resp["TableDescription"]
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_tags():
client = boto3.client("dynamodb", region_name="us-east-1")
resp = client.create_table(
TableName="test-streams",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
Tags=[{"Key": "tk", "Value": "tv"}],
)
resp = client.list_tags_of_resource(
ResourceArn=resp["TableDescription"]["TableArn"]
)
assert resp["Tags"] == [{"Key": "tk", "Value": "tv"}]
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_pay_per_request():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
)
actual = client.describe_table(TableName="test1")["Table"]
assert actual["BillingModeSummary"] == {"BillingMode": "PAY_PER_REQUEST"}
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 0,
"WriteCapacityUnits": 0,
}
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table__provisioned_throughput():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 2, "WriteCapacityUnits": 3},
)
actual = client.describe_table(TableName="test1")["Table"]
assert actual["BillingModeSummary"] == {"BillingMode": "PROVISIONED"}
assert actual["ProvisionedThroughput"] == {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 2,
"WriteCapacityUnits": 3,
}
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_without_specifying_throughput():
dynamodb_client = boto3.client("dynamodb", region_name="us-east-1")
with pytest.raises(ClientError) as exc:
dynamodb_client.create_table(
TableName="my-table",
AttributeDefinitions=[
{"AttributeName": "some_field", "AttributeType": "S"}
],
KeySchema=[{"AttributeName": "some_field", "KeyType": "HASH"}],
BillingMode="PROVISIONED",
StreamSpecification={"StreamEnabled": False, "StreamViewType": "NEW_IMAGE"},
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
2022-02-10 20:09:45 +00:00
)
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_error_pay_per_request_with_provisioned_param():
client = boto3.client("dynamodb", region_name="us-east-1")
with pytest.raises(ClientError) as exc:
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
BillingMode="PAY_PER_REQUEST",
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
2022-02-10 20:09:45 +00:00
)
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_ssespecification__false():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
SSESpecification={"Enabled": False},
)
actual = client.describe_table(TableName="test1")["Table"]
assert "SSEDescription" not in actual
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_ssespecification__true():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
SSESpecification={"Enabled": True},
)
actual = client.describe_table(TableName="test1")["Table"]
assert "SSEDescription" in actual
assert actual["SSEDescription"]["Status"] == "ENABLED"
assert actual["SSEDescription"]["SSEType"] == "KMS"
# Default KMS key for DynamoDB
assert actual["SSEDescription"]["KMSMasterKeyArn"].startswith("arn:aws:kms")
2022-02-10 20:09:45 +00:00
2022-03-09 17:57:25 +00:00
@mock_dynamodb
2022-02-10 20:09:45 +00:00
def test_create_table_with_ssespecification__custom_kms_key():
client = boto3.client("dynamodb", region_name="us-east-1")
client.create_table(
TableName="test1",
AttributeDefinitions=[
{"AttributeName": "client", "AttributeType": "S"},
{"AttributeName": "app", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "client", "KeyType": "HASH"},
{"AttributeName": "app", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
SSESpecification={"Enabled": True, "KMSMasterKeyId": "custom-kms-key"},
)
actual = client.describe_table(TableName="test1")["Table"]
assert "SSEDescription" in actual
assert actual["SSEDescription"]["Status"] == "ENABLED"
assert actual["SSEDescription"]["SSEType"] == "KMS"
assert actual["SSEDescription"]["KMSMasterKeyArn"] == "custom-kms-key"
2023-11-04 10:37:32 +00:00
@pytest.mark.aws_verified
@dynamodb_aws_verified(create_table=False)
def test_create_table__specify_non_key_column():
2023-11-04 10:37:32 +00:00
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
with pytest.raises(ClientError) as exc:
dynamodb.create_table(
TableName="unknown-key-type",
KeySchema=[
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "SORT"},
],
AttributeDefinitions=[
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'SORT' at 'keySchema.2.member.keyType' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
)
2023-11-04 10:37:32 +00:00
# Verify we get the same message for Global Secondary Indexes
with pytest.raises(ClientError) as exc:
dynamodb.create_table(
TableName="unknown-key-type",
KeySchema=[
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
GlobalSecondaryIndexes=[
{
"IndexName": "TestGSI",
# Note that the attributes are not declared, which is also invalid
# But AWS trips over the KeyType=SORT first
"KeySchema": [
{"AttributeName": "n/a", "KeyType": "HASH"},
{"AttributeName": "sth", "KeyType": "SORT"},
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5,
},
}
],
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'SORT' at 'globalSecondaryIndexes.1.member.keySchema.2.member.keyType' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
)
2023-11-04 10:37:32 +00:00
# Verify we get the same message for Local Secondary Indexes
with pytest.raises(ClientError) as exc:
dynamodb.create_table(
TableName="unknown-key-type",
KeySchema=[
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"},
],
AttributeDefinitions=[
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
LocalSecondaryIndexes=[
{
"IndexName": "test_lsi",
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "lsi_range_key", "KeyType": "SORT"},
],
"Projection": {"ProjectionType": "ALL"},
}
],
)
err = exc.value.response["Error"]
assert err["Code"] == "ValidationException"
assert (
err["Message"]
== "1 validation error detected: Value 'SORT' at 'localSecondaryIndexes.1.member.keySchema.2.member.keyType' failed to satisfy constraint: Member must satisfy enum value set: [HASH, RANGE]"
)