TimestreamWrite - improvements (#4971)
This commit is contained in:
parent
2929f3ee35
commit
afa34ffd8d
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -224,7 +224,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [ 3.8 ]
|
python-version: [ 3.8 ]
|
||||||
part: ["aa", "ab", "ac", "ad", "ae", "af"]
|
part: ["aa", "ab", "ac", "ad", "ae", "af", "ag"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -259,7 +259,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd moto-terraform-tests
|
cd moto-terraform-tests
|
||||||
bin/list-tests -i ../tests/terraform-tests.success.txt -e ../tests/terraform-tests.failures.txt > tftestlist.txt
|
bin/list-tests -i ../tests/terraform-tests.success.txt -e ../tests/terraform-tests.failures.txt > tftestlist.txt
|
||||||
split -n l/6 tftestlist.txt tf-split-
|
split -n l/7 tftestlist.txt tf-split-
|
||||||
cd ..
|
cd ..
|
||||||
- name: Run Terraform Tests
|
- name: Run Terraform Tests
|
||||||
run: |
|
run: |
|
||||||
|
@ -5472,7 +5472,7 @@
|
|||||||
|
|
||||||
## timestream-write
|
## timestream-write
|
||||||
<details>
|
<details>
|
||||||
<summary>80% implemented</summary>
|
<summary>100% implemented</summary>
|
||||||
|
|
||||||
- [X] create_database
|
- [X] create_database
|
||||||
- [X] create_table
|
- [X] create_table
|
||||||
@ -5483,9 +5483,9 @@
|
|||||||
- [X] describe_table
|
- [X] describe_table
|
||||||
- [X] list_databases
|
- [X] list_databases
|
||||||
- [X] list_tables
|
- [X] list_tables
|
||||||
- [ ] list_tags_for_resource
|
- [X] list_tags_for_resource
|
||||||
- [ ] tag_resource
|
- [X] tag_resource
|
||||||
- [ ] untag_resource
|
- [X] untag_resource
|
||||||
- [X] update_database
|
- [X] update_database
|
||||||
- [X] update_table
|
- [X] update_table
|
||||||
- [X] write_records
|
- [X] write_records
|
||||||
|
@ -34,9 +34,9 @@ timestream-write
|
|||||||
- [X] describe_table
|
- [X] describe_table
|
||||||
- [X] list_databases
|
- [X] list_databases
|
||||||
- [X] list_tables
|
- [X] list_tables
|
||||||
- [ ] list_tags_for_resource
|
- [X] list_tags_for_resource
|
||||||
- [ ] tag_resource
|
- [X] tag_resource
|
||||||
- [ ] untag_resource
|
- [X] untag_resource
|
||||||
- [X] update_database
|
- [X] update_database
|
||||||
- [X] update_table
|
- [X] update_table
|
||||||
- [X] write_records
|
- [X] write_records
|
||||||
|
@ -1 +1,9 @@
|
|||||||
"""Exceptions raised by the timestreamwrite service."""
|
"""Exceptions raised by the timestreamwrite service."""
|
||||||
|
from moto.core.exceptions import JsonRESTError
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceNotFound(JsonRESTError):
|
||||||
|
error_type = "com.amazonaws.timestream.v20181101#ResourceNotFoundException"
|
||||||
|
|
||||||
|
def __init__(self, msg):
|
||||||
|
super().__init__(ResourceNotFound.error_type, msg)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
|
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
|
||||||
from moto.core.utils import BackendDict
|
from moto.core.utils import BackendDict
|
||||||
|
from moto.utilities.tagging_service import TaggingService
|
||||||
|
from .exceptions import ResourceNotFound
|
||||||
|
|
||||||
|
|
||||||
class TimestreamTable(BaseModel):
|
class TimestreamTable(BaseModel):
|
||||||
@ -7,7 +9,10 @@ class TimestreamTable(BaseModel):
|
|||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.name = table_name
|
self.name = table_name
|
||||||
self.db_name = db_name
|
self.db_name = db_name
|
||||||
self.retention_properties = retention_properties
|
self.retention_properties = retention_properties or {
|
||||||
|
"MemoryStoreRetentionPeriodInHours": 123,
|
||||||
|
"MagneticStoreRetentionPeriodInDays": 123,
|
||||||
|
}
|
||||||
self.records = []
|
self.records = []
|
||||||
|
|
||||||
def update(self, retention_properties):
|
def update(self, retention_properties):
|
||||||
@ -34,7 +39,9 @@ class TimestreamDatabase(BaseModel):
|
|||||||
def __init__(self, region_name, database_name, kms_key_id):
|
def __init__(self, region_name, database_name, kms_key_id):
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.name = database_name
|
self.name = database_name
|
||||||
self.kms_key_id = kms_key_id
|
self.kms_key_id = (
|
||||||
|
kms_key_id or f"arn:aws:kms:{region_name}:{ACCOUNT_ID}:key/default_key"
|
||||||
|
)
|
||||||
self.tables = dict()
|
self.tables = dict()
|
||||||
|
|
||||||
def update(self, kms_key_id):
|
def update(self, kms_key_id):
|
||||||
@ -59,6 +66,8 @@ class TimestreamDatabase(BaseModel):
|
|||||||
del self.tables[table_name]
|
del self.tables[table_name]
|
||||||
|
|
||||||
def describe_table(self, table_name):
|
def describe_table(self, table_name):
|
||||||
|
if table_name not in self.tables:
|
||||||
|
raise ResourceNotFound(f"The table {table_name} does not exist.")
|
||||||
return self.tables[table_name]
|
return self.tables[table_name]
|
||||||
|
|
||||||
def list_tables(self):
|
def list_tables(self):
|
||||||
@ -83,16 +92,20 @@ class TimestreamWriteBackend(BaseBackend):
|
|||||||
def __init__(self, region_name):
|
def __init__(self, region_name):
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.databases = dict()
|
self.databases = dict()
|
||||||
|
self.tagging_service = TaggingService()
|
||||||
|
|
||||||
def create_database(self, database_name, kms_key_id):
|
def create_database(self, database_name, kms_key_id, tags):
|
||||||
database = TimestreamDatabase(self.region_name, database_name, kms_key_id)
|
database = TimestreamDatabase(self.region_name, database_name, kms_key_id)
|
||||||
self.databases[database_name] = database
|
self.databases[database_name] = database
|
||||||
|
self.tagging_service.tag_resource(database.arn, tags)
|
||||||
return database
|
return database
|
||||||
|
|
||||||
def delete_database(self, database_name):
|
def delete_database(self, database_name):
|
||||||
del self.databases[database_name]
|
del self.databases[database_name]
|
||||||
|
|
||||||
def describe_database(self, database_name):
|
def describe_database(self, database_name):
|
||||||
|
if database_name not in self.databases:
|
||||||
|
raise ResourceNotFound(f"The database {database_name} does not exist.")
|
||||||
return self.databases[database_name]
|
return self.databases[database_name]
|
||||||
|
|
||||||
def list_databases(self):
|
def list_databases(self):
|
||||||
@ -103,9 +116,10 @@ class TimestreamWriteBackend(BaseBackend):
|
|||||||
database.update(kms_key_id=kms_key_id)
|
database.update(kms_key_id=kms_key_id)
|
||||||
return database
|
return database
|
||||||
|
|
||||||
def create_table(self, database_name, table_name, retention_properties):
|
def create_table(self, database_name, table_name, retention_properties, tags):
|
||||||
database = self.describe_database(database_name)
|
database = self.describe_database(database_name)
|
||||||
table = database.create_table(table_name, retention_properties)
|
table = database.create_table(table_name, retention_properties)
|
||||||
|
self.tagging_service.tag_resource(table.arn, tags)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
def delete_table(self, database_name, table_name):
|
def delete_table(self, database_name, table_name):
|
||||||
@ -147,6 +161,15 @@ class TimestreamWriteBackend(BaseBackend):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def list_tags_for_resource(self, resource_arn):
|
||||||
|
return self.tagging_service.list_tags_for_resource(resource_arn)
|
||||||
|
|
||||||
|
def tag_resource(self, resource_arn, tags):
|
||||||
|
self.tagging_service.tag_resource(resource_arn, tags)
|
||||||
|
|
||||||
|
def untag_resource(self, resource_arn, tag_keys):
|
||||||
|
self.tagging_service.untag_resource_using_names(resource_arn, tag_keys)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
region_name = self.region_name
|
region_name = self.region_name
|
||||||
self.__dict__ = {}
|
self.__dict__ = {}
|
||||||
|
@ -16,8 +16,9 @@ class TimestreamWriteResponse(BaseResponse):
|
|||||||
def create_database(self):
|
def create_database(self):
|
||||||
database_name = self._get_param("DatabaseName")
|
database_name = self._get_param("DatabaseName")
|
||||||
kms_key_id = self._get_param("KmsKeyId")
|
kms_key_id = self._get_param("KmsKeyId")
|
||||||
|
tags = self._get_param("Tags")
|
||||||
database = self.timestreamwrite_backend.create_database(
|
database = self.timestreamwrite_backend.create_database(
|
||||||
database_name=database_name, kms_key_id=kms_key_id
|
database_name=database_name, kms_key_id=kms_key_id, tags=tags
|
||||||
)
|
)
|
||||||
return json.dumps(dict(Database=database.description()))
|
return json.dumps(dict(Database=database.description()))
|
||||||
|
|
||||||
@ -49,8 +50,9 @@ class TimestreamWriteResponse(BaseResponse):
|
|||||||
database_name = self._get_param("DatabaseName")
|
database_name = self._get_param("DatabaseName")
|
||||||
table_name = self._get_param("TableName")
|
table_name = self._get_param("TableName")
|
||||||
retention_properties = self._get_param("RetentionProperties")
|
retention_properties = self._get_param("RetentionProperties")
|
||||||
|
tags = self._get_param("Tags")
|
||||||
table = self.timestreamwrite_backend.create_table(
|
table = self.timestreamwrite_backend.create_table(
|
||||||
database_name, table_name, retention_properties
|
database_name, table_name, retention_properties, tags
|
||||||
)
|
)
|
||||||
return json.dumps(dict(Table=table.description()))
|
return json.dumps(dict(Table=table.description()))
|
||||||
|
|
||||||
@ -97,3 +99,20 @@ class TimestreamWriteResponse(BaseResponse):
|
|||||||
def describe_endpoints(self):
|
def describe_endpoints(self):
|
||||||
resp = self.timestreamwrite_backend.describe_endpoints()
|
resp = self.timestreamwrite_backend.describe_endpoints()
|
||||||
return json.dumps(resp)
|
return json.dumps(resp)
|
||||||
|
|
||||||
|
def list_tags_for_resource(self):
|
||||||
|
resource_arn = self._get_param("ResourceARN")
|
||||||
|
tags = self.timestreamwrite_backend.list_tags_for_resource(resource_arn)
|
||||||
|
return json.dumps(tags)
|
||||||
|
|
||||||
|
def tag_resource(self):
|
||||||
|
resource_arn = self._get_param("ResourceARN")
|
||||||
|
tags = self._get_param("Tags")
|
||||||
|
self.timestreamwrite_backend.tag_resource(resource_arn, tags)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def untag_resource(self):
|
||||||
|
resource_arn = self._get_param("ResourceARN")
|
||||||
|
tag_keys = self._get_param("TagKeys")
|
||||||
|
self.timestreamwrite_backend.untag_resource(resource_arn, tag_keys)
|
||||||
|
return "{}"
|
||||||
|
@ -131,6 +131,8 @@ TestAccAWSSQSQueuePolicy
|
|||||||
TestAccAWSSSMDocument
|
TestAccAWSSSMDocument
|
||||||
TestAccAWSSsmDocumentDataSource
|
TestAccAWSSsmDocumentDataSource
|
||||||
TestAccAWSSsmParameterDataSource
|
TestAccAWSSsmParameterDataSource
|
||||||
|
TestAccAWSTimestreamWriteDatabase
|
||||||
|
TestAccAWSTimestreamWriteTable
|
||||||
TestAccAWSUserGroupMembership
|
TestAccAWSUserGroupMembership
|
||||||
TestAccAWSUserPolicyAttachment
|
TestAccAWSUserPolicyAttachment
|
||||||
TestAccAWSUserSSHKey
|
TestAccAWSUserSSHKey
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import boto3
|
import boto3
|
||||||
|
import pytest
|
||||||
import sure # noqa # pylint: disable=unused-import
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
from moto import mock_timestreamwrite
|
from moto import mock_timestreamwrite
|
||||||
from moto.core import ACCOUNT_ID
|
from moto.core import ACCOUNT_ID
|
||||||
|
|
||||||
@ -15,7 +18,9 @@ def test_create_database_simple():
|
|||||||
)
|
)
|
||||||
database.should.have.key("DatabaseName").equals("mydatabase")
|
database.should.have.key("DatabaseName").equals("mydatabase")
|
||||||
database.should.have.key("TableCount").equals(0)
|
database.should.have.key("TableCount").equals(0)
|
||||||
database.shouldnt.have.key("KmsKeyId")
|
database.should.have.key("KmsKeyId").equals(
|
||||||
|
f"arn:aws:kms:us-east-1:{ACCOUNT_ID}:key/default_key"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_timestreamwrite
|
@mock_timestreamwrite
|
||||||
@ -51,6 +56,16 @@ def test_describe_database():
|
|||||||
database.should.have.key("KmsKeyId").equal("mykey")
|
database.should.have.key("KmsKeyId").equal("mykey")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_describe_unknown_database():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
ts.describe_database(DatabaseName="unknown")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
err["Message"].should.equal("The database unknown does not exist.")
|
||||||
|
|
||||||
|
|
||||||
@mock_timestreamwrite
|
@mock_timestreamwrite
|
||||||
def test_list_databases():
|
def test_list_databases():
|
||||||
ts = boto3.client("timestream-write", region_name="us-east-1")
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
@ -73,6 +88,7 @@ def test_list_databases():
|
|||||||
"Arn": f"arn:aws:timestream:us-east-1:{ACCOUNT_ID}:database/db_without",
|
"Arn": f"arn:aws:timestream:us-east-1:{ACCOUNT_ID}:database/db_without",
|
||||||
"DatabaseName": "db_without",
|
"DatabaseName": "db_without",
|
||||||
"TableCount": 0,
|
"TableCount": 0,
|
||||||
|
"KmsKeyId": f"arn:aws:kms:us-east-1:{ACCOUNT_ID}:key/default_key",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import time
|
import time
|
||||||
import boto3
|
import boto3
|
||||||
|
import pytest
|
||||||
import sure # noqa # pylint: disable=unused-import
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
from moto import mock_timestreamwrite, settings
|
from moto import mock_timestreamwrite, settings
|
||||||
from moto.core import ACCOUNT_ID
|
from moto.core import ACCOUNT_ID
|
||||||
|
|
||||||
@ -46,7 +49,12 @@ def test_create_table_without_retention_properties():
|
|||||||
table.should.have.key("TableName").equal("mytable")
|
table.should.have.key("TableName").equal("mytable")
|
||||||
table.should.have.key("DatabaseName").equal("mydatabase")
|
table.should.have.key("DatabaseName").equal("mydatabase")
|
||||||
table.should.have.key("TableStatus").equal("ACTIVE")
|
table.should.have.key("TableStatus").equal("ACTIVE")
|
||||||
table.shouldnt.have.key("RetentionProperties")
|
table.should.have.key("RetentionProperties").equals(
|
||||||
|
{
|
||||||
|
"MemoryStoreRetentionPeriodInHours": 123,
|
||||||
|
"MagneticStoreRetentionPeriodInDays": 123,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_timestreamwrite
|
@mock_timestreamwrite
|
||||||
@ -78,6 +86,18 @@ def test_describe_table():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_describe_unknown_database():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
ts.create_database(DatabaseName="mydatabase")
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
ts.describe_table(DatabaseName="mydatabase", TableName="unknown")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
err["Message"].should.equal("The table unknown does not exist.")
|
||||||
|
|
||||||
|
|
||||||
@mock_timestreamwrite
|
@mock_timestreamwrite
|
||||||
def test_create_multiple_tables():
|
def test_create_multiple_tables():
|
||||||
ts = boto3.client("timestream-write", region_name="us-east-1")
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
91
tests/test_timestreamwrite/test_timestreamwrite_tagging.py
Normal file
91
tests/test_timestreamwrite/test_timestreamwrite_tagging.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import boto3
|
||||||
|
|
||||||
|
from moto import mock_timestreamwrite
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_list_tagging_for_table_without_tags():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
ts.create_database(DatabaseName="mydatabase")
|
||||||
|
|
||||||
|
resp = ts.create_table(
|
||||||
|
DatabaseName="mydatabase",
|
||||||
|
TableName="mytable",
|
||||||
|
RetentionProperties={
|
||||||
|
"MemoryStoreRetentionPeriodInHours": 7,
|
||||||
|
"MagneticStoreRetentionPeriodInDays": 42,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
table_arn = resp["Table"]["Arn"]
|
||||||
|
|
||||||
|
resp = ts.list_tags_for_resource(ResourceARN=table_arn)
|
||||||
|
resp.should.have.key("Tags").equals([])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_list_tagging_for_table_with_tags():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
ts.create_database(DatabaseName="mydatabase")
|
||||||
|
|
||||||
|
resp = ts.create_table(
|
||||||
|
DatabaseName="mydatabase",
|
||||||
|
TableName="mytable",
|
||||||
|
RetentionProperties={
|
||||||
|
"MemoryStoreRetentionPeriodInHours": 7,
|
||||||
|
"MagneticStoreRetentionPeriodInDays": 42,
|
||||||
|
},
|
||||||
|
Tags=[{"Key": "k1", "Value": "v1"}],
|
||||||
|
)
|
||||||
|
table_arn = resp["Table"]["Arn"]
|
||||||
|
|
||||||
|
resp = ts.list_tags_for_resource(ResourceARN=table_arn)
|
||||||
|
resp.should.have.key("Tags").equals([{"Key": "k1", "Value": "v1"}])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_list_tagging_for_database_without_tags():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
db_arn = ts.create_database(DatabaseName="mydatabase")["Database"]["Arn"]
|
||||||
|
|
||||||
|
resp = ts.list_tags_for_resource(ResourceARN=db_arn)
|
||||||
|
resp.should.have.key("Tags").equals([])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_list_tagging_for_database_with_tags():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
db_arn = ts.create_database(
|
||||||
|
DatabaseName="mydatabase", Tags=[{"Key": "k1", "Value": "v1"}]
|
||||||
|
)["Database"]["Arn"]
|
||||||
|
|
||||||
|
resp = ts.list_tags_for_resource(ResourceARN=db_arn)
|
||||||
|
resp.should.have.key("Tags").equals([{"Key": "k1", "Value": "v1"}])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_timestreamwrite
|
||||||
|
def test_tag_and_untag_database():
|
||||||
|
ts = boto3.client("timestream-write", region_name="us-east-1")
|
||||||
|
db_arn = ts.create_database(
|
||||||
|
DatabaseName="mydatabase", Tags=[{"Key": "k1", "Value": "v1"}]
|
||||||
|
)["Database"]["Arn"]
|
||||||
|
|
||||||
|
ts.tag_resource(
|
||||||
|
ResourceARN=db_arn,
|
||||||
|
Tags=[{"Key": "k2", "Value": "v2"}, {"Key": "k3", "Value": "v3"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = ts.list_tags_for_resource(ResourceARN=db_arn)
|
||||||
|
resp.should.have.key("Tags").equals(
|
||||||
|
[
|
||||||
|
{"Key": "k1", "Value": "v1"},
|
||||||
|
{"Key": "k2", "Value": "v2"},
|
||||||
|
{"Key": "k3", "Value": "v3"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
ts.untag_resource(ResourceARN=db_arn, TagKeys=["k2"])
|
||||||
|
|
||||||
|
resp = ts.list_tags_for_resource(ResourceARN=db_arn)
|
||||||
|
resp.should.have.key("Tags").equals(
|
||||||
|
[{"Key": "k1", "Value": "v1"}, {"Key": "k3", "Value": "v3"}]
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user