Skip to content

Commit

Permalink
ElastiCache: Add the create_cache_cluster, delete_cache_cluster a…
Browse files Browse the repository at this point in the history
…nd `describe_cache_clusters` methods support (#6754)
  • Loading branch information
jfmainville authored Sep 2, 2023
1 parent 4bc41ae commit d78db35
Show file tree
Hide file tree
Showing 6 changed files with 928 additions and 29 deletions.
10 changes: 5 additions & 5 deletions docs/docs/services/elasticache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ elasticache
- [ ] batch_stop_update_action
- [ ] complete_migration
- [ ] copy_snapshot
- [ ] create_cache_cluster
- [x] create_cache_cluster
- [ ] create_cache_parameter_group
- [ ] create_cache_security_group
- [ ] create_cache_subnet_group
Expand All @@ -44,7 +44,7 @@ elasticache
- [ ] create_user_group
- [ ] decrease_node_groups_in_global_replication_group
- [ ] decrease_replica_count
- [ ] delete_cache_cluster
- [x] delete_cache_cluster
- [ ] delete_cache_parameter_group
- [ ] delete_cache_security_group
- [ ] delete_cache_subnet_group
Expand All @@ -53,7 +53,7 @@ elasticache
- [ ] delete_snapshot
- [X] delete_user
- [ ] delete_user_group
- [ ] describe_cache_clusters
- [x] describe_cache_clusters
- [ ] describe_cache_engine_versions
- [ ] describe_cache_parameter_groups
- [ ] describe_cache_parameters
Expand All @@ -70,10 +70,10 @@ elasticache
- [ ] describe_update_actions
- [ ] describe_user_groups
- [X] describe_users

Only the `user_id` parameter is currently supported.
Pagination is not yet implemented.


- [ ] disassociate_global_replication_group
- [ ] failover_global_replication_group
Expand Down
5 changes: 4 additions & 1 deletion moto/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import importlib
import sys
from contextlib import ContextDecorator
from moto.core.models import BaseMockAWS
from typing import Any, Callable, List, Optional, TypeVar

from moto.core.models import BaseMockAWS

TEST_METHOD = TypeVar("TEST_METHOD", bound=Callable[..., Any])

Expand Down Expand Up @@ -189,6 +189,9 @@ def f(*args: Any, **kwargs: Any) -> Any:
mock_xray_client = lazy_load(".xray", "mock_xray_client")
mock_wafv2 = lazy_load(".wafv2", "mock_wafv2")
mock_textract = lazy_load(".textract", "mock_textract")
mock_elasticache = lazy_load(
".elasticache", "mock_elasticache", boto3_name="elasticache"
)


class MockAll(ContextDecorator):
Expand Down
23 changes: 23 additions & 0 deletions moto/elasticache/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Any

from moto.core.exceptions import RESTError

EXCEPTION_RESPONSE = """<?xml version="1.0"?>
Expand Down Expand Up @@ -58,3 +59,25 @@ class UserNotFound(ElastiCacheException):

def __init__(self, user_id: str):
super().__init__("UserNotFound", message=f"User {user_id} not found.")


class CacheClusterAlreadyExists(ElastiCacheException):

code = 404

def __init__(self, cache_cluster_id: str):
super().__init__(
"CacheClusterAlreadyExists",
message=f"Cache cluster {cache_cluster_id} already exists.",
),


class CacheClusterNotFound(ElastiCacheException):

code = 404

def __init__(self, cache_cluster_id: str):
super().__init__(
"CacheClusterNotFound",
message=f"Cache cluster {cache_cluster_id} not found.",
)
242 changes: 240 additions & 2 deletions moto/elasticache/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from typing import List, Optional
from datetime import datetime
from typing import List, Optional, Dict, Any, Tuple

from moto.core import BaseBackend, BackendDict, BaseModel

from .exceptions import UserAlreadyExists, UserNotFound
from .exceptions import (
UserAlreadyExists,
UserNotFound,
CacheClusterAlreadyExists,
CacheClusterNotFound,
)
from ..moto_api._internal import mock_random


class User(BaseModel):
Expand Down Expand Up @@ -29,6 +37,93 @@ def __init__(
self.arn = f"arn:aws:elasticache:{self.region}:{account_id}:user:{self.id}"


class CacheCluster(BaseModel):
def __init__(
self,
account_id: str,
region_name: str,
cache_cluster_id: str,
replication_group_id: Optional[str],
az_mode: Optional[str],
preferred_availability_zone: Optional[str],
num_cache_nodes: Optional[int],
cache_node_type: Optional[str],
engine: Optional[str],
engine_version: Optional[str],
cache_parameter_group_name: Optional[str],
cache_subnet_group_name: Optional[str],
transit_encryption_enabled: Optional[bool],
network_type: Optional[str],
ip_discovery: Optional[str],
snapshot_name: Optional[str],
preferred_maintenance_window: Optional[str],
port: Optional[int],
notification_topic_arn: Optional[str],
auto_minor_version_upgrade: Optional[bool],
snapshot_retention_limit: Optional[int],
snapshot_window: Optional[str],
auth_token: Optional[str],
outpost_mode: Optional[str],
preferred_outpost_arn: Optional[str],
preferred_availability_zones: Optional[List[str]],
cache_security_group_names: Optional[List[str]],
security_group_ids: Optional[List[str]],
tags: Optional[List[Dict[str, str]]],
snapshot_arns: Optional[List[str]],
preferred_outpost_arns: Optional[List[str]],
log_delivery_configurations: List[Dict[str, Any]],
cache_node_ids_to_remove: Optional[List[str]],
cache_node_ids_to_reboot: Optional[List[str]],
):
if tags is None:
tags = []

self.cache_cluster_id = cache_cluster_id
self.az_mode = az_mode
self.preferred_availability_zone = preferred_availability_zone
self.preferred_availability_zones = preferred_availability_zones or []
self.engine = engine or "redis"
self.engine_version = engine_version
if engine == "redis":
self.num_cache_nodes = 1
self.replication_group_id = replication_group_id
self.snapshot_arns = snapshot_arns or []
self.snapshot_name = snapshot_name
self.snapshot_window = snapshot_window
if engine == "memcached":
if num_cache_nodes is None:
self.num_cache_nodes = 1
elif 1 <= num_cache_nodes <= 40:
self.num_cache_nodes = num_cache_nodes
self.cache_node_type = cache_node_type
self.cache_parameter_group_name = cache_parameter_group_name
self.cache_subnet_group_name = cache_subnet_group_name
self.cache_security_group_names = cache_security_group_names or []
self.security_group_ids = security_group_ids or []
self.tags = tags
self.preferred_maintenance_window = preferred_maintenance_window
self.port = port or 6379
self.notification_topic_arn = notification_topic_arn
self.auto_minor_version_upgrade = auto_minor_version_upgrade
self.snapshot_retention_limit = snapshot_retention_limit or 0
self.auth_token = auth_token
self.outpost_mode = outpost_mode
self.preferred_outpost_arn = preferred_outpost_arn
self.preferred_outpost_arns = preferred_outpost_arns or []
self.log_delivery_configurations = log_delivery_configurations or []
self.transit_encryption_enabled = transit_encryption_enabled
self.network_type = network_type
self.ip_discovery = ip_discovery
self.cache_node_ids_to_remove = cache_node_ids_to_remove
self.cache_node_ids_to_reboot = cache_node_ids_to_reboot

self.cache_cluster_create_time = datetime.utcnow()
self.auth_token_last_modified_date = datetime.utcnow()
self.cache_cluster_status = "available"
self.arn = f"arn:aws:elasticache:{region_name}:{account_id}:{cache_cluster_id}"
self.cache_node_id = str(mock_random.uuid4())


class ElastiCacheBackend(BaseBackend):
"""Implementation of ElastiCache APIs."""

Expand All @@ -45,6 +140,45 @@ def __init__(self, region_name: str, account_id: str):
no_password_required=True,
)

# Define the cache_clusters dictionary to detect duplicates
self.cache_clusters = dict()
self.cache_clusters["default"] = CacheCluster(
account_id=self.account_id,
region_name=self.region_name,
cache_cluster_id="default",
replication_group_id=None,
az_mode=None,
preferred_availability_zone=None,
num_cache_nodes=1,
cache_node_type=None,
engine="redis",
engine_version=None,
cache_parameter_group_name=None,
cache_subnet_group_name=None,
transit_encryption_enabled=True,
network_type=None,
ip_discovery=None,
snapshot_name=None,
preferred_maintenance_window=None,
port=6379,
notification_topic_arn=None,
auto_minor_version_upgrade=True,
snapshot_retention_limit=0,
snapshot_window=None,
auth_token=None,
outpost_mode=None,
preferred_outpost_arn=None,
preferred_availability_zones=[],
cache_security_group_names=[],
security_group_ids=[],
tags=[],
snapshot_arns=[],
preferred_outpost_arns=[],
log_delivery_configurations=[],
cache_node_ids_to_remove=[],
cache_node_ids_to_reboot=[],
)

def create_user(
self,
user_id: str,
Expand Down Expand Up @@ -92,5 +226,109 @@ def describe_users(self, user_id: Optional[str]) -> List[User]:
raise UserNotFound(user_id)
return list(self.users.values())

def create_cache_cluster(
self,
cache_cluster_id: str,
replication_group_id: str,
az_mode: str,
preferred_availability_zone: str,
num_cache_nodes: int,
cache_node_type: str,
engine: str,
engine_version: str,
cache_parameter_group_name: str,
cache_subnet_group_name: str,
transit_encryption_enabled: bool,
network_type: str,
ip_discovery: str,
snapshot_name: str,
preferred_maintenance_window: str,
port: int,
notification_topic_arn: str,
auto_minor_version_upgrade: bool,
snapshot_retention_limit: int,
snapshot_window: str,
auth_token: str,
outpost_mode: str,
preferred_outpost_arn: str,
preferred_availability_zones: List[str],
cache_security_group_names: List[str],
security_group_ids: List[str],
tags: List[Dict[str, str]],
snapshot_arns: List[str],
preferred_outpost_arns: List[str],
log_delivery_configurations: List[Dict[str, Any]],
cache_node_ids_to_remove: List[str],
cache_node_ids_to_reboot: List[str],
) -> CacheCluster:
if cache_cluster_id in self.cache_clusters:
raise CacheClusterAlreadyExists(cache_cluster_id)
cache_cluster = CacheCluster(
account_id=self.account_id,
region_name=self.region_name,
cache_cluster_id=cache_cluster_id,
replication_group_id=replication_group_id,
az_mode=az_mode,
preferred_availability_zone=preferred_availability_zone,
preferred_availability_zones=preferred_availability_zones,
num_cache_nodes=num_cache_nodes,
cache_node_type=cache_node_type,
engine=engine,
engine_version=engine_version,
cache_parameter_group_name=cache_parameter_group_name,
cache_subnet_group_name=cache_subnet_group_name,
cache_security_group_names=cache_security_group_names,
security_group_ids=security_group_ids,
tags=tags,
snapshot_arns=snapshot_arns,
snapshot_name=snapshot_name,
preferred_maintenance_window=preferred_maintenance_window,
port=port,
notification_topic_arn=notification_topic_arn,
auto_minor_version_upgrade=auto_minor_version_upgrade,
snapshot_retention_limit=snapshot_retention_limit,
snapshot_window=snapshot_window,
auth_token=auth_token,
outpost_mode=outpost_mode,
preferred_outpost_arn=preferred_outpost_arn,
preferred_outpost_arns=preferred_outpost_arns,
log_delivery_configurations=log_delivery_configurations,
transit_encryption_enabled=transit_encryption_enabled,
network_type=network_type,
ip_discovery=ip_discovery,
cache_node_ids_to_remove=cache_node_ids_to_remove,
cache_node_ids_to_reboot=cache_node_ids_to_reboot,
)
self.cache_clusters[cache_cluster_id] = cache_cluster
return cache_cluster

def describe_cache_clusters(
self,
cache_cluster_id: str,
max_records: int,
marker: str,
) -> Tuple[str, List[CacheCluster]]:
if marker is None:
marker = str(mock_random.uuid4())
if max_records is None:
max_records = 100
if cache_cluster_id:
if cache_cluster_id in self.cache_clusters:
cache_cluster = self.cache_clusters[cache_cluster_id]
return marker, [cache_cluster]
else:
raise CacheClusterNotFound(cache_cluster_id)
cache_clusters = list(self.cache_clusters.values())[:max_records]

return marker, cache_clusters

def delete_cache_cluster(self, cache_cluster_id: str) -> CacheCluster:
if cache_cluster_id:
if cache_cluster_id in self.cache_clusters:
cache_cluster = self.cache_clusters[cache_cluster_id]
cache_cluster.cache_cluster_status = "deleting"
return cache_cluster
raise CacheClusterNotFound(cache_cluster_id)


elasticache_backends = BackendDict(ElastiCacheBackend, "elasticache")
Loading

0 comments on commit d78db35

Please sign in to comment.