Added restore table to point in time DynamoDB method (#4627)

This commit is contained in:
Josh Feierman 2021-11-24 16:26:21 -05:00 committed by GitHub
parent 4f2e7f706d
commit 7d44df5266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 1 deletions

View File

@ -61,7 +61,7 @@ dynamodb
- [X] put_item
- [X] query
- [X] restore_table_from_backup
- [ ] restore_table_to_point_in_time
- [X] restore_table_to_point_in_time
- [X] scan
- [X] tag_resource
- [X] transact_get_items

View File

@ -1020,6 +1020,36 @@ class RestoredTable(Table):
return result
class RestoredPITTable(Table):
def __init__(self, name, source):
params = self._parse_params_from_table(source)
super(RestoredPITTable, self).__init__(name, **params)
self.indexes = copy.deepcopy(source.indexes)
self.global_indexes = copy.deepcopy(source.global_indexes)
self.items = copy.deepcopy(source.items)
# Restore Attrs
self.source_table_arn = source.table_arn
self.restore_date_time = self.created_at
@staticmethod
def _parse_params_from_table(table):
params = {
"schema": copy.deepcopy(table.schema),
"attr": copy.deepcopy(table.attr),
"throughput": copy.deepcopy(table.throughput),
}
return params
def describe(self, base_key="TableDescription"):
result = super(RestoredPITTable, self).describe(base_key=base_key)
result[base_key]["RestoreSummary"] = {
"SourceTableArn": self.source_table_arn,
"RestoreDateTime": unix_time(self.restore_date_time),
"RestoreInProgress": False,
}
return result
class Backup(object):
def __init__(
self, backend, name, table, status=None, type_=None,
@ -1700,6 +1730,22 @@ class DynamoDBBackend(BaseBackend):
self.tables[target_table_name] = new_table
return new_table
"""
Currently this only accepts the source and target table elements, and will
copy all items from the source without respect to other arguments.
"""
def restore_table_to_point_in_time(self, target_table_name, source_table_name):
source = self.get_table(source_table_name)
if source is None:
raise KeyError()
existing_table = self.get_table(target_table_name)
if existing_table is not None:
raise ValueError()
new_table = RestoredPITTable(target_table_name, source)
self.tables[target_table_name] = new_table
return new_table
######################
# LIST of methods where the logic completely resides in responses.py
# Duplicated here so that the implementation coverage script is aware

View File

@ -1203,3 +1203,19 @@ class DynamoHandler(BaseResponse):
except ValueError:
er = "com.amazonaws.dynamodb.v20111205#TableAlreadyExistsException"
return self.error(er, "Table already exists: %s" % target_table_name)
def restore_table_to_point_in_time(self):
body = self.body
target_table_name = body.get("TargetTableName")
source_table_name = body.get("SourceTableName")
try:
restored_table = self.dynamodb_backend.restore_table_to_point_in_time(
target_table_name, source_table_name
)
return dynamo_json_dump(restored_table.describe())
except KeyError:
er = "com.amazonaws.dynamodb.v20111205#SourceTableNotFoundException"
return self.error(er, "Source table not found: %s" % source_table_name)
except ValueError:
er = "com.amazonaws.dynamodb.v20111205#TableAlreadyExistsException"
return self.error(er, "Table already exists: %s" % target_table_name)

View File

@ -5524,6 +5524,76 @@ def test_restore_table_from_backup():
summary.should.have.key("RestoreInProgress").should.equal(False)
@mock_dynamodb2
def test_restore_table_to_point_in_time():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
resp = client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
table = resp.get("TableDescription")
for i in range(5):
client.put_item(TableName=table_name, Item={"id": {"S": "item %d" % i}})
restored_table_name = "restored-from-pit"
restored = client.restore_table_to_point_in_time(
TargetTableName=restored_table_name, SourceTableName=table_name
).get("TableDescription")
restored.should.have.key("TableName").should.equal(restored_table_name)
restored.should.have.key("KeySchema").should.equal(table["KeySchema"])
restored.should.have.key("TableStatus")
restored.should.have.key("ItemCount").should.equal(5)
restored.should.have.key("TableArn").should.contain(restored_table_name)
restored.should.have.key("RestoreSummary").should.be.a(dict)
summary = restored.get("RestoreSummary")
summary.should.have.key("SourceTableArn").should.equal(table["TableArn"])
summary.should.have.key("RestoreDateTime").should.be.a(datetime)
summary.should.have.key("RestoreInProgress").should.equal(False)
@mock_dynamodb2
def test_restore_table_to_point_in_time_raises_error_when_source_not_exist():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
restored_table_name = "restored-from-pit"
with pytest.raises(ClientError) as ex:
client.restore_table_to_point_in_time(
TargetTableName=restored_table_name, SourceTableName=table_name
)
error = ex.value.response["Error"]
error["Code"].should.equal("SourceTableNotFoundException")
error["Message"].should.equal("Source table not found: %s" % table_name)
@mock_dynamodb2
def test_restore_table_to_point_in_time_raises_error_when_dest_exist():
client = boto3.client("dynamodb", "us-east-1")
table_name = "test-table"
restored_table_name = "restored-from-pit"
client.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.create_table(
TableName=restored_table_name,
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
with pytest.raises(ClientError) as ex:
client.restore_table_to_point_in_time(
TargetTableName=restored_table_name, SourceTableName=table_name
)
error = ex.value.response["Error"]
error["Code"].should.equal("TableAlreadyExistsException")
error["Message"].should.equal("Table already exists: %s" % restored_table_name)
@mock_dynamodb2
def test_delete_non_existent_backup_raises_error():
client = boto3.client("dynamodb", "us-east-1")