Kinesis:create_stream() now supports stream-mode=OnDemand (#5444)
This commit is contained in:
parent
930c58bd13
commit
aef092d535
@ -23,6 +23,15 @@ class StreamNotFoundError(ResourceNotFoundError):
|
|||||||
super().__init__(f"Stream {stream_name} under account {account_id} not found.")
|
super().__init__(f"Stream {stream_name} under account {account_id} not found.")
|
||||||
|
|
||||||
|
|
||||||
|
class StreamCannotBeUpdatedError(BadRequest):
|
||||||
|
def __init__(self, stream_name, account_id):
|
||||||
|
super().__init__()
|
||||||
|
message = f"Request is invalid. Stream {stream_name} under account {account_id} is in On-Demand mode."
|
||||||
|
self.description = json.dumps(
|
||||||
|
{"message": message, "__type": "ValidationException"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ShardNotFoundError(ResourceNotFoundError):
|
class ShardNotFoundError(ResourceNotFoundError):
|
||||||
def __init__(self, shard_id, stream, account_id):
|
def __init__(self, shard_id, stream, account_id):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
@ -12,6 +12,7 @@ from moto.utilities.utils import md5_hash
|
|||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
ConsumerNotFound,
|
ConsumerNotFound,
|
||||||
StreamNotFoundError,
|
StreamNotFoundError,
|
||||||
|
StreamCannotBeUpdatedError,
|
||||||
ShardNotFoundError,
|
ShardNotFoundError,
|
||||||
ResourceInUseError,
|
ResourceInUseError,
|
||||||
ResourceNotFoundError,
|
ResourceNotFoundError,
|
||||||
@ -164,7 +165,13 @@ class Shard(BaseModel):
|
|||||||
|
|
||||||
class Stream(CloudFormationModel):
|
class Stream(CloudFormationModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, stream_name, shard_count, retention_period_hours, account_id, region_name
|
self,
|
||||||
|
stream_name,
|
||||||
|
shard_count,
|
||||||
|
stream_mode,
|
||||||
|
retention_period_hours,
|
||||||
|
account_id,
|
||||||
|
region_name,
|
||||||
):
|
):
|
||||||
self.stream_name = stream_name
|
self.stream_name = stream_name
|
||||||
self.creation_datetime = datetime.datetime.now().strftime(
|
self.creation_datetime = datetime.datetime.now().strftime(
|
||||||
@ -177,10 +184,11 @@ class Stream(CloudFormationModel):
|
|||||||
self.tags = {}
|
self.tags = {}
|
||||||
self.status = "ACTIVE"
|
self.status = "ACTIVE"
|
||||||
self.shard_count = None
|
self.shard_count = None
|
||||||
|
self.stream_mode = stream_mode or {"StreamMode": "PROVISIONED"}
|
||||||
|
if self.stream_mode.get("StreamMode", "") == "ON_DEMAND":
|
||||||
|
shard_count = 4
|
||||||
self.init_shards(shard_count)
|
self.init_shards(shard_count)
|
||||||
self.retention_period_hours = (
|
self.retention_period_hours = retention_period_hours or 24
|
||||||
retention_period_hours if retention_period_hours else 24
|
|
||||||
)
|
|
||||||
self.shard_level_metrics = []
|
self.shard_level_metrics = []
|
||||||
self.encryption_type = "NONE"
|
self.encryption_type = "NONE"
|
||||||
self.key_id = None
|
self.key_id = None
|
||||||
@ -289,6 +297,10 @@ class Stream(CloudFormationModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_shard_count(self, target_shard_count):
|
def update_shard_count(self, target_shard_count):
|
||||||
|
if self.stream_mode.get("StreamMode", "") == "ON_DEMAND":
|
||||||
|
raise StreamCannotBeUpdatedError(
|
||||||
|
stream_name=self.stream_name, account_id=self.account_id
|
||||||
|
)
|
||||||
current_shard_count = len([s for s in self.shards.values() if s.is_open])
|
current_shard_count = len([s for s in self.shards.values() if s.is_open])
|
||||||
if current_shard_count == target_shard_count:
|
if current_shard_count == target_shard_count:
|
||||||
return
|
return
|
||||||
@ -393,8 +405,12 @@ class Stream(CloudFormationModel):
|
|||||||
"StreamARN": self.arn,
|
"StreamARN": self.arn,
|
||||||
"StreamName": self.stream_name,
|
"StreamName": self.stream_name,
|
||||||
"StreamStatus": self.status,
|
"StreamStatus": self.status,
|
||||||
|
"StreamModeDetails": self.stream_mode,
|
||||||
|
"RetentionPeriodHours": self.retention_period_hours,
|
||||||
"StreamCreationTimestamp": self.creation_datetime,
|
"StreamCreationTimestamp": self.creation_datetime,
|
||||||
|
"EnhancedMonitoring": [{"ShardLevelMetrics": self.shard_level_metrics}],
|
||||||
"OpenShardCount": self.shard_count,
|
"OpenShardCount": self.shard_count,
|
||||||
|
"EncryptionType": self.encryption_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,7 +437,7 @@ class Stream(CloudFormationModel):
|
|||||||
|
|
||||||
backend = kinesis_backends[account_id][region_name]
|
backend = kinesis_backends[account_id][region_name]
|
||||||
stream = backend.create_stream(
|
stream = backend.create_stream(
|
||||||
resource_name, shard_count, retention_period_hours
|
resource_name, shard_count, retention_period_hours=retention_period_hours
|
||||||
)
|
)
|
||||||
if any(tags):
|
if any(tags):
|
||||||
backend.add_tags_to_stream(stream.stream_name, tags)
|
backend.add_tags_to_stream(stream.stream_name, tags)
|
||||||
@ -510,15 +526,18 @@ class KinesisBackend(BaseBackend):
|
|||||||
service_region, zones, "kinesis", special_service_name="kinesis-streams"
|
service_region, zones, "kinesis", special_service_name="kinesis-streams"
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_stream(self, stream_name, shard_count, retention_period_hours):
|
def create_stream(
|
||||||
|
self, stream_name, shard_count, stream_mode=None, retention_period_hours=None
|
||||||
|
):
|
||||||
if stream_name in self.streams:
|
if stream_name in self.streams:
|
||||||
raise ResourceInUseError(stream_name)
|
raise ResourceInUseError(stream_name)
|
||||||
stream = Stream(
|
stream = Stream(
|
||||||
stream_name,
|
stream_name,
|
||||||
shard_count,
|
shard_count,
|
||||||
retention_period_hours,
|
stream_mode=stream_mode,
|
||||||
self.account_id,
|
retention_period_hours=retention_period_hours,
|
||||||
self.region_name,
|
account_id=self.account_id,
|
||||||
|
region_name=self.region_name,
|
||||||
)
|
)
|
||||||
self.streams[stream_name] = stream
|
self.streams[stream_name] = stream
|
||||||
return stream
|
return stream
|
||||||
|
@ -19,9 +19,9 @@ class KinesisResponse(BaseResponse):
|
|||||||
def create_stream(self):
|
def create_stream(self):
|
||||||
stream_name = self.parameters.get("StreamName")
|
stream_name = self.parameters.get("StreamName")
|
||||||
shard_count = self.parameters.get("ShardCount")
|
shard_count = self.parameters.get("ShardCount")
|
||||||
retention_period_hours = self.parameters.get("RetentionPeriodHours")
|
stream_mode = self.parameters.get("StreamModeDetails")
|
||||||
self.kinesis_backend.create_stream(
|
self.kinesis_backend.create_stream(
|
||||||
stream_name, shard_count, retention_period_hours
|
stream_name, shard_count, stream_mode=stream_mode
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -134,6 +134,9 @@ iam:
|
|||||||
- TestAccIAMUserSSHKeyDataSource_
|
- TestAccIAMUserSSHKeyDataSource_
|
||||||
iot:
|
iot:
|
||||||
- TestAccIoTEndpointDataSource
|
- TestAccIoTEndpointDataSource
|
||||||
|
kinesis:
|
||||||
|
- TestAccKinesisStream_basic
|
||||||
|
- TestAccKinesisStream_disappear
|
||||||
kms:
|
kms:
|
||||||
- TestAccKMSAlias
|
- TestAccKMSAlias
|
||||||
- TestAccKMSGrant_arn
|
- TestAccKMSGrant_arn
|
||||||
|
@ -13,6 +13,29 @@ from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
|||||||
import sure # noqa # pylint: disable=unused-import
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
|
||||||
|
|
||||||
|
@mock_kinesis
|
||||||
|
def test_stream_creation_on_demand():
|
||||||
|
client = boto3.client("kinesis", region_name="eu-west-1")
|
||||||
|
client.create_stream(
|
||||||
|
StreamName="my_stream", StreamModeDetails={"StreamMode": "ON_DEMAND"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# AWS starts with 4 shards by default
|
||||||
|
shard_list = client.list_shards(StreamName="my_stream")["Shards"]
|
||||||
|
shard_list.should.have.length_of(4)
|
||||||
|
|
||||||
|
# Cannot update-shard-count when we're in on-demand mode
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.update_shard_count(
|
||||||
|
StreamName="my_stream", TargetShardCount=3, ScalingType="UNIFORM_SCALING"
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationException")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
f"Request is invalid. Stream my_stream under account {ACCOUNT_ID} is in On-Demand mode."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_kinesis
|
@mock_kinesis
|
||||||
def test_describe_non_existent_stream_boto3():
|
def test_describe_non_existent_stream_boto3():
|
||||||
client = boto3.client("kinesis", region_name="us-west-2")
|
client = boto3.client("kinesis", region_name="us-west-2")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user