Repository: cloud-custodian/cloud-custodian

Issue:
Add ElastiCache cluster and snapshot tags and actions
Augment ElastiCache cluster and snapshot with tags.
Add cluster `delete` and `snapshot` actions and snapshot `delete` actions.



Discussion:

Resolving patch:
diff --git a/c7n/resources/__init__.py b/c7n/resources/__init__.py
--- a/c7n/resources/__init__.py
+++ b/c7n/resources/__init__.py
@@ -17,35 +17,35 @@
 
 
 def load_resources():
-    import account
-    import ami
-    import acm
-    import apigw
-    import asg
-    import awslambda
-    import cache
-    import cfn
-    import cloudfront
-    import cw
-    import dynamodb
-    import ebs
-    import ec2
-    import ecr
-    import ecs
-    import efs
-    import elasticsearch
-    import elb
-    import emr
-    import firehose
-    import glacier
-    import iam
-    import kinesis
-    import kms
-    import redshift
-    import rds
-    import rdscluster
-    import route53
-    import s3
-    import sns
-    import sqs
-    import vpc
+    import c7n.resources.account
+    import c7n.resources.ami
+    import c7n.resources.acm
+    import c7n.resources.apigw
+    import c7n.resources.asg
+    import c7n.resources.awslambda
+    import c7n.resources.cfn
+    import c7n.resources.cloudfront
+    import c7n.resources.cw
+    import c7n.resources.dynamodb
+    import c7n.resources.ebs
+    import c7n.resources.ec2
+    import c7n.resources.ecr
+    import c7n.resources.ecs
+    import c7n.resources.efs
+    import c7n.resources.elasticache
+    import c7n.resources.elasticsearch
+    import c7n.resources.elb
+    import c7n.resources.emr
+    import c7n.resources.firehose
+    import c7n.resources.glacier
+    import c7n.resources.iam
+    import c7n.resources.kinesis
+    import c7n.resources.kms
+    import c7n.resources.redshift
+    import c7n.resources.rds
+    import c7n.resources.rdscluster
+    import c7n.resources.route53
+    import c7n.resources.s3
+    import c7n.resources.sns
+    import c7n.resources.sqs
+    import c7n.resources.vpc
diff --git a/c7n/resources/cache.py b/c7n/resources/cache.py
deleted file mode 100644
--- a/c7n/resources/cache.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2016 Capital One Services, LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from c7n.query import QueryResourceManager
-from c7n.manager import resources
-
-
-@resources.register('cache-cluster')
-class CacheCluster(QueryResourceManager):
-
-    resource_type = 'aws.elasticache.cluster'
-
-
-@resources.register('cache-subnet-group')
-class ClusterSubnetGroup(QueryResourceManager):
-
-    resource_type = 'aws.elasticache.subnet-group'
-
-
-@resources.register('cache-snapshot')
-class CacheSnapshot(QueryResourceManager):
-
-    resource_type = 'aws.elasticache.snapshot'
diff --git a/c7n/resources/elasticache.py b/c7n/resources/elasticache.py
new file mode 100644
--- /dev/null
+++ b/c7n/resources/elasticache.py
@@ -0,0 +1,271 @@
+# Copyright 2016 Capital One Services, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import functools
+import logging
+
+from datetime import datetime
+
+from concurrent.futures import as_completed
+from dateutil.tz import tzutc
+from dateutil.parser import parse
+
+from c7n.actions import ActionRegistry, BaseAction
+from c7n.filters import FilterRegistry, AgeFilter, OPERATORS
+from c7n.manager import resources
+from c7n.query import QueryResourceManager
+from c7n.utils import (
+    local_session, get_account_id, generate_arn,
+    get_retry, chunks, snapshot_identifier, type_schema)
+
+log = logging.getLogger('custodian.elasticache')
+
+filters = FilterRegistry('elasticache.filters')
+actions = ActionRegistry('elasticache.actions')
+
+
+@resources.register('cache-cluster')
+class ElastiCacheCluster(QueryResourceManager):
+
+    resource_type = 'aws.elasticache.cluster'
+    filter_registry = filters
+    action_registry = actions
+    _generate_arn = _account_id = None
+    retry = staticmethod(get_retry(('Throttled',)))
+
+    @property
+    def account_id(self):
+        if self._account_id is None:
+            session = local_session(self.session_factory)
+            self._account_id = get_account_id(session)
+        return self._account_id
+
+    @property
+    def generate_arn(self):
+        if self._generate_arn is None:
+            self._generate_arn = functools.partial(
+                generate_arn,
+                'elasticache',
+                region=self.config.region,
+                account_id=self.account_id,
+                resource_type='cluster',
+                separator=':')
+        return self._generate_arn
+
+    def augment(self, clusters):
+        filter(None, _elasticache_cluster_tags(
+            self.get_model(),
+            clusters, self.session_factory, self.executor_factory,
+            self.generate_arn, self.retry))
+        return clusters
+
+
+@actions.register('delete')
+class DeleteElastiCacheCluster(BaseAction):
+
+    schema = type_schema(
+        'delete', **{'skip-snapshot': {'type': 'boolean'}})
+
+    def process(self, clusters):
+        skip = self.data.get('skip-snapshot', False)
+        client = local_session(self.manager.session_factory).client('elasticache')
+
+        clusters_to_delete = []
+        replication_groups_to_delete = set()
+        for cluster in clusters:
+            if cluster.get('ReplicationGroupId', ''):
+                replication_groups_to_delete.add(cluster['ReplicationGroupId'])
+            else:
+                clusters_to_delete.append(cluster)
+
+        for cluster in clusters_to_delete:
+            params = {'CacheClusterId': cluster['CacheClusterId']}
+            if _cluster_eligible_for_snapshot(cluster) and not skip:
+                params['FinalSnapshotIdentifier'] = snapshot_identifier(
+                    'Final', cluster['CacheClusterId'])
+            client.delete_cache_cluster(**params)
+
+            self.log.info(
+                'Deleted ElastiCache cluster: %s',
+                cluster['CacheClusterId'])
+
+        for replication_group in replication_groups_to_delete:
+            params = {'ReplicationGroupId': replication_group,
+                      'RetainPrimaryCluster': False}
+            if not skip:
+                params['FinalSnapshotIdentifier'] = snapshot_identifier(
+                    'Final', replication_group)
+            client.delete_replication_group(**params)
+
+            self.log.info(
+                'Deleted ElastiCache replication group: %s',
+                replication_group)
+
+
+@actions.register('snapshot')
+class SnapshotElastiCacheCluster(BaseAction):
+
+    def process(self, clusters):
+        with self.executor_factory(max_workers=3) as w:
+            futures = []
+            for cluster in clusters:
+                if not _cluster_eligible_for_snapshot(cluster):
+                    continue
+                futures.append(w.submit(
+                    self.process_cluster_snapshot,
+                    cluster))
+
+            for f in as_completed(futures):
+                if f.exception():
+                    self.log.error(
+                        "Exception creating ElastiCache cluster snapshot  \n %s",
+                        f.exception())
+        return clusters
+
+    def process_cluster_snapshot(self, cluster):
+        c = local_session(self.manager.session_factory).client('elasticache')
+        c.create_snapshot(
+            SnapshotName=snapshot_identifier(
+                'Backup',
+                cluster['CacheClusterId']),
+            CacheClusterId=cluster['CacheClusterId'])
+
+
+@resources.register('cache-subnet-group')
+class ElastiCacheSubnetGroup(QueryResourceManager):
+
+    resource_type = 'aws.elasticache.subnet-group'
+
+
+@resources.register('cache-snapshot')
+class ElastiCacheSnapshot(QueryResourceManager):
+
+    resource_type = 'aws.elasticache.snapshot'
+    filter_registry = FilterRegistry('elasticache-snapshot.filters')
+    action_registry = ActionRegistry('elasticache-snapshot.actions')
+    _generate_arn = _account_id = None
+    retry = staticmethod(get_retry(('Throttled',)))
+
+    @property
+    def account_id(self):
+        if self._account_id is None:
+            session = local_session(self.session_factory)
+            self._account_id = get_account_id(session)
+        return self._account_id
+
+    @property
+    def generate_arn(self):
+        if self._generate_arn is None:
+            self._generate_arn = functools.partial(
+                generate_arn,
+                'elasticache',
+                region=self.config.region,
+                account_id=self.account_id,
+                resource_type='snapshot',
+                separator=':')
+        return self._generate_arn
+
+    def augment(self, clusters):
+        filter(None, _elasticache_snapshot_tags(
+            self.get_model(),
+            clusters, self.session_factory, self.executor_factory,
+            self.generate_arn, self.retry))
+        return clusters
+
+
+@ElastiCacheSnapshot.filter_registry.register('age')
+class ElastiCacheSnapshotAge(AgeFilter):
+
+    schema = type_schema(
+        'age', days={'type': 'number'},
+        op={'type': 'string', 'enum': OPERATORS.keys()})
+
+    date_attribute = 'dummy'
+
+    def get_resource_date(self, snapshot):
+        """ Override superclass method as there is no single snapshot date attribute.
+        """
+        def to_datetime(v):
+            if not isinstance(v, datetime):
+                v = parse(v)
+            if not v.tzinfo:
+                v = v.replace(tzinfo=tzutc())
+            return v
+
+        # Return the earliest of the node snaphot creation times.
+        return min([to_datetime(ns['SnapshotCreateTime'])
+                    for ns in snapshot['NodeSnapshots']])
+
+
+@ElastiCacheSnapshot.action_registry.register('delete')
+class DeleteElastiCacheSnapshot(BaseAction):
+
+    def process(self, snapshots):
+        log.info("Deleting %d ElastiCache snapshots", len(snapshots))
+        with self.executor_factory(max_workers=3) as w:
+            futures = []
+            for snapshot_set in chunks(reversed(snapshots), size=50):
+                futures.append(
+                    w.submit(self.process_snapshot_set, snapshot_set))
+                for f in as_completed(futures):
+                    if f.exception():
+                        self.log.error(
+                            "Exception deleting snapshot set \n %s",
+                            f.exception())
+        return snapshots
+
+    def process_snapshot_set(self, snapshots_set):
+        c = local_session(self.manager.session_factory).client('elasticache')
+        for s in snapshots_set:
+            c.delete_snapshot(SnapshotName=s['SnapshotName'])
+
+
+def _elasticache_cluster_tags(
+        model, clusters, session_factory, executor_factory, generator, retry):
+    """ Augment ElastiCache clusters with their respective tags
+    """
+
+    def process_tags(cluster):
+        client = local_session(session_factory).client('elasticache')
+        arn = generator(cluster[model.id])
+        tag_list = retry(
+            client.list_tags_for_resource,
+            ResourceName=arn)['TagList']
+        cluster['Tags'] = tag_list or []
+        return cluster
+
+    with executor_factory(max_workers=2) as w:
+        return list(w.map(process_tags, clusters))
+
+
+def _elasticache_snapshot_tags(
+        model, snapshots, session_factory, executor_factory, generator, retry):
+    """ Augment ElastiCache snapshots with their respective tags
+    """
+
+    def process_tags(snapshot):
+        client = local_session(session_factory).client('elasticache')
+        arn = generator(snapshot[model.id])
+        tag_list = retry(
+            client.list_tags_for_resource,
+            ResourceName=arn)['TagList']
+        snapshot['Tags'] = tag_list or []
+        return snapshot
+
+    with executor_factory(max_workers=2) as w:
+        return list(w.map(process_tags, snapshots))
+
+
+def _cluster_eligible_for_snapshot(cluster):
+    return cluster['Engine'] != 'memcached'


Test patch:
diff --git a/tests/data/placebo/test_elasticache_cluster_delete/elasticache.DeleteCacheCluster_1.json b/tests/data/placebo/test_elasticache_cluster_delete/elasticache.DeleteCacheCluster_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_delete/elasticache.DeleteCacheCluster_1.json
@@ -0,0 +1,55 @@
+{
+    "status_code": 200, 
+    "data": {
+        "CacheCluster": {
+            "Engine": "memcached", 
+            "CacheParameterGroup": {
+                "CacheNodeIdsToReboot": [], 
+                "CacheParameterGroupName": "default.memcached1.4", 
+                "ParameterApplyStatus": "in-sync"
+            }, 
+            "CacheClusterId": "memcached-cluster-1", 
+            "PreferredAvailabilityZone": "us-west-2a", 
+            "ConfigurationEndpoint": {
+                "Port": 11211, 
+                "Address": "memcached-cluster-1.i2seyy.cfg.usw2.cache.amazonaws.com"
+            }, 
+            "CacheSecurityGroups": [], 
+            "CacheClusterCreateTime": {
+                "hour": 14, 
+                "__class__": "datetime", 
+                "month": 9, 
+                "second": 28, 
+                "microsecond": 888000, 
+                "year": 2016, 
+                "day": 7, 
+                "minute": 41
+            }, 
+            "AutoMinorVersionUpgrade": true, 
+            "CacheClusterStatus": "deleting", 
+            "NumCacheNodes": 2, 
+            "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+            "SecurityGroups": [
+                {
+                    "Status": "active", 
+                    "SecurityGroupId": "sg-f9cc4d9f"
+                }
+            ], 
+            "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+            "EngineVersion": "1.4.24", 
+            "PendingModifiedValues": {}, 
+            "PreferredMaintenanceWindow": "wed:02:04-wed:04:04", 
+            "CacheNodeType": "cache.t2.micro"
+        }, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "2ae5e5cb-7528-11e6-ab08-8559232b3224", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "2ae5e5cb-7528-11e6-ab08-8559232b3224", 
+                "date": "Wed, 07 Sep 2016 18:23:26 GMT", 
+                "content-length": "1739", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_delete/elasticache.DescribeCacheClusters_1.json b/tests/data/placebo/test_elasticache_cluster_delete/elasticache.DescribeCacheClusters_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_delete/elasticache.DescribeCacheClusters_1.json
@@ -0,0 +1,174 @@
+{
+    "status_code": 200, 
+    "data": {
+        "CacheClusters": [
+            {
+                "Engine": "memcached", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.memcached1.4", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "CacheClusterId": "memcached-cluster-1", 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "ConfigurationEndpoint": {
+                    "Port": 11211, 
+                    "Address": "memcached-cluster-1.i2seyy.cfg.usw2.cache.amazonaws.com"
+                }, 
+                "CacheSecurityGroups": [], 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 28, 
+                    "microsecond": 888000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "NumCacheNodes": 2, 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "1.4.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "wed:02:04-wed:04:04", 
+                "CacheNodeType": "cache.t2.micro"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 0, 
+                "CacheClusterId": "redis-rg-1-001", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 29, 
+                    "microsecond": 184000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 2, 
+                "CacheClusterId": "redis-rg-1-002", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 45, 
+                    "microsecond": 666000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2b", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 0, 
+                "CacheClusterId": "redis-rg-1-003", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 54, 
+                    "microsecond": 436000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2c", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "baf6adeb-750a-11e6-bcfa-055709a7c18d", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "baf6adeb-750a-11e6-bcfa-055709a7c18d", 
+                "date": "Wed, 07 Sep 2016 14:52:42 GMT", 
+                "content-length": "6358", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_delete/elasticache.ListTagsForResource_1.json b/tests/data/placebo/test_elasticache_cluster_delete/elasticache.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_delete/elasticache.ListTagsForResource_1.json
@@ -0,0 +1,25 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "deab54a0-7512-11e6-8c33-e95c27c07d8a", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "deab54a0-7512-11e6-8c33-e95c27c07d8a", 
+                "date": "Wed, 07 Sep 2016 15:50:58 GMT", 
+                "content-length": "461", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TagList": [
+            {
+                "Value": "V1", 
+                "Key": "K1"
+            }, 
+            {
+                "Value": "V2", 
+                "Key": "K2"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_delete/iam.ListRoles_1.json b/tests/data/placebo/test_elasticache_cluster_delete/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_delete/iam.ListRoles_1.json
@@ -0,0 +1,30 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AAKVUG6oouqUWrxlFukIbjuEpYbnFs07xCfDiKrqFwQXJ5FtAS+E41rwcx8anK0PS96pdOV2t4dX2nmpIR0+E7T7", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAIBRFOWJZSGZJEZVAK", 
+                "CreateDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 56, 
+                    "microsecond": 0, 
+                    "year": 2015, 
+                    "day": 24, 
+                    "minute": 55
+                }, 
+                "RoleName": "BaseIAMRole", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::619193117841:role/BaseIAMRole"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "d4b5d85e-2023-11e6-964c-7b76c530a910"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_simple/elasticache.DescribeCacheClusters_1.json b/tests/data/placebo/test_elasticache_cluster_simple/elasticache.DescribeCacheClusters_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_simple/elasticache.DescribeCacheClusters_1.json
@@ -0,0 +1,174 @@
+{
+    "status_code": 200, 
+    "data": {
+        "CacheClusters": [
+            {
+                "Engine": "memcached", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.memcached1.4", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "CacheClusterId": "memcached-cluster-1", 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "ConfigurationEndpoint": {
+                    "Port": 11211, 
+                    "Address": "memcached-cluster-1.i2seyy.cfg.usw2.cache.amazonaws.com"
+                }, 
+                "CacheSecurityGroups": [], 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 28, 
+                    "microsecond": 888000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "NumCacheNodes": 2, 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "1.4.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "wed:02:04-wed:04:04", 
+                "CacheNodeType": "cache.t2.micro"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 0, 
+                "CacheClusterId": "redis-rg-1-001", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 29, 
+                    "microsecond": 184000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 2, 
+                "CacheClusterId": "redis-rg-1-002", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 45, 
+                    "microsecond": 666000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2b", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 0, 
+                "CacheClusterId": "redis-rg-1-003", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 54, 
+                    "microsecond": 436000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2c", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "baf6adeb-750a-11e6-bcfa-055709a7c18d", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "baf6adeb-750a-11e6-bcfa-055709a7c18d", 
+                "date": "Wed, 07 Sep 2016 14:52:42 GMT", 
+                "content-length": "6358", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_simple/elasticache.ListTagsForResource_1.json b/tests/data/placebo/test_elasticache_cluster_simple/elasticache.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_simple/elasticache.ListTagsForResource_1.json
@@ -0,0 +1,25 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "deab54a0-7512-11e6-8c33-e95c27c07d8a", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "deab54a0-7512-11e6-8c33-e95c27c07d8a", 
+                "date": "Wed, 07 Sep 2016 15:50:58 GMT", 
+                "content-length": "461", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TagList": [
+            {
+                "Value": "V1", 
+                "Key": "K1"
+            }, 
+            {
+                "Value": "V2", 
+                "Key": "K2"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_simple/iam.ListRoles_1.json b/tests/data/placebo/test_elasticache_cluster_simple/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_simple/iam.ListRoles_1.json
@@ -0,0 +1,30 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AAKVUG6oouqUWrxlFukIbjuEpYbnFs07xCfDiKrqFwQXJ5FtAS+E41rwcx8anK0PS96pdOV2t4dX2nmpIR0+E7T7", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAIBRFOWJZSGZJEZVAK", 
+                "CreateDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 56, 
+                    "microsecond": 0, 
+                    "year": 2015, 
+                    "day": 24, 
+                    "minute": 55
+                }, 
+                "RoleName": "BaseIAMRole", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::619193117841:role/BaseIAMRole"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "d4b5d85e-2023-11e6-964c-7b76c530a910"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.CreateSnapshot_1.json b/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.CreateSnapshot_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.CreateSnapshot_1.json
@@ -0,0 +1,60 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Snapshot": {
+            "Engine": "redis", 
+            "CacheParameterGroupName": "default.redis2.8", 
+            "VpcId": "vpc-4a9ff72e", 
+            "CacheClusterId": "redis-rg-1-003", 
+            "SnapshotRetentionLimit": 0, 
+            "NumCacheNodes": 1, 
+            "SnapshotName": "test-snapshot-1", 
+            "CacheClusterCreateTime": {
+                "hour": 14, 
+                "__class__": "datetime", 
+                "month": 9, 
+                "second": 54, 
+                "microsecond": 436000, 
+                "year": 2016, 
+                "day": 7, 
+                "minute": 46
+            }, 
+            "AutoMinorVersionUpgrade": true, 
+            "PreferredAvailabilityZone": "us-west-2c", 
+            "SnapshotStatus": "creating", 
+            "SnapshotSource": "manual", 
+            "SnapshotWindow": "06:00-07:00", 
+            "EngineVersion": "2.8.24", 
+            "NodeSnapshots": [
+                {
+                    "CacheSize": "", 
+                    "CacheNodeId": "0001", 
+                    "CacheNodeCreateTime": {
+                        "hour": 14, 
+                        "__class__": "datetime", 
+                        "month": 9, 
+                        "second": 54, 
+                        "microsecond": 436000, 
+                        "year": 2016, 
+                        "day": 7, 
+                        "minute": 46
+                    }
+                }
+            ], 
+            "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+            "Port": 6379, 
+            "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+            "CacheNodeType": "cache.m3.medium"
+        }, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "8ba7815d-7521-11e6-a6f3-c1eb89d96391", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "8ba7815d-7521-11e6-a6f3-c1eb89d96391", 
+                "date": "Wed, 07 Sep 2016 17:36:01 GMT", 
+                "content-length": "1511", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.DescribeCacheClusters_1.json b/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.DescribeCacheClusters_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.DescribeCacheClusters_1.json
@@ -0,0 +1,174 @@
+{
+    "status_code": 200, 
+    "data": {
+        "CacheClusters": [
+            {
+                "Engine": "memcached", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.memcached1.4", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "CacheClusterId": "memcached-cluster-1", 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "ConfigurationEndpoint": {
+                    "Port": 11211, 
+                    "Address": "memcached-cluster-1.i2seyy.cfg.usw2.cache.amazonaws.com"
+                }, 
+                "CacheSecurityGroups": [], 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 28, 
+                    "microsecond": 888000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "NumCacheNodes": 2, 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "1.4.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "wed:02:04-wed:04:04", 
+                "CacheNodeType": "cache.t2.micro"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 0, 
+                "CacheClusterId": "redis-rg-1-001", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 29, 
+                    "microsecond": 184000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 2, 
+                "CacheClusterId": "redis-rg-1-002", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 45, 
+                    "microsecond": 666000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2b", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroup": {
+                    "CacheNodeIdsToReboot": [], 
+                    "CacheParameterGroupName": "default.redis2.8", 
+                    "ParameterApplyStatus": "in-sync"
+                }, 
+                "SnapshotRetentionLimit": 0, 
+                "CacheClusterId": "redis-rg-1-003", 
+                "CacheSecurityGroups": [], 
+                "NumCacheNodes": 1, 
+                "SnapshotWindow": "06:00-07:00", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 54, 
+                    "microsecond": 436000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "ReplicationGroupId": "redis-rg-1", 
+                "AutoMinorVersionUpgrade": true, 
+                "CacheClusterStatus": "available", 
+                "PreferredAvailabilityZone": "us-west-2c", 
+                "ClientDownloadLandingPage": "https://console.aws.amazon.com/elasticache/home#client-download:", 
+                "SecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "SecurityGroupId": "sg-f9cc4d9f"
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "EngineVersion": "2.8.24", 
+                "PendingModifiedValues": {}, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "baf6adeb-750a-11e6-bcfa-055709a7c18d", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "baf6adeb-750a-11e6-bcfa-055709a7c18d", 
+                "date": "Wed, 07 Sep 2016 14:52:42 GMT", 
+                "content-length": "6358", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.ListTagsForResource_1.json b/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_snapshot/elasticache.ListTagsForResource_1.json
@@ -0,0 +1,25 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "deab54a0-7512-11e6-8c33-e95c27c07d8a", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "deab54a0-7512-11e6-8c33-e95c27c07d8a", 
+                "date": "Wed, 07 Sep 2016 15:50:58 GMT", 
+                "content-length": "461", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TagList": [
+            {
+                "Value": "V1", 
+                "Key": "K1"
+            }, 
+            {
+                "Value": "V2", 
+                "Key": "K2"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_cluster_snapshot/iam.ListRoles_1.json b/tests/data/placebo/test_elasticache_cluster_snapshot/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_cluster_snapshot/iam.ListRoles_1.json
@@ -0,0 +1,30 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AAKVUG6oouqUWrxlFukIbjuEpYbnFs07xCfDiKrqFwQXJ5FtAS+E41rwcx8anK0PS96pdOV2t4dX2nmpIR0+E7T7", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAIBRFOWJZSGZJEZVAK", 
+                "CreateDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 56, 
+                    "microsecond": 0, 
+                    "year": 2015, 
+                    "day": 24, 
+                    "minute": 55
+                }, 
+                "RoleName": "BaseIAMRole", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::619193117841:role/BaseIAMRole"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "d4b5d85e-2023-11e6-964c-7b76c530a910"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot/elasticache.DescribeSnapshots_1.json b/tests/data/placebo/test_elasticache_snapshot/elasticache.DescribeSnapshots_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot/elasticache.DescribeSnapshots_1.json
@@ -0,0 +1,182 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "96686044-7513-11e6-8c33-e95c27c07d8a", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "96686044-7513-11e6-8c33-e95c27c07d8a", 
+                "date": "Wed, 07 Sep 2016 15:56:06 GMT", 
+                "content-length": "4479", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "Snapshots": [
+            {
+                "Engine": "redis", 
+                "CacheParameterGroupName": "default.redis2.8", 
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheClusterId": "redis-rg-1-001", 
+                "SnapshotRetentionLimit": 0, 
+                "NumCacheNodes": 1, 
+                "SnapshotName": "redis-snapshot-1", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 29, 
+                    "microsecond": 184000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "SnapshotStatus": "available", 
+                "SnapshotSource": "manual", 
+                "SnapshotWindow": "06:00-07:00", 
+                "EngineVersion": "2.8.24", 
+                "NodeSnapshots": [
+                    {
+                        "CacheSize": "4 MB", 
+                        "SnapshotCreateTime": {
+                            "hour": 15, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 55, 
+                            "microsecond": 0, 
+                            "year": 2016, 
+                            "day": 3, 
+                            "minute": 52
+                        }, 
+                        "CacheNodeId": "0001", 
+                        "CacheNodeCreateTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 29, 
+                            "microsecond": 184000, 
+                            "year": 2016, 
+                            "day": 3, 
+                            "minute": 41
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "Port": 6379, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroupName": "default.redis2.8", 
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheClusterId": "redis-rg-1-002", 
+                "SnapshotRetentionLimit": 2, 
+                "NumCacheNodes": 1, 
+                "SnapshotName": "redis-snapshot-2", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 45, 
+                    "microsecond": 666000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "PreferredAvailabilityZone": "us-west-2b", 
+                "SnapshotStatus": "creating", 
+                "SnapshotSource": "manual", 
+                "SnapshotWindow": "06:00-07:00", 
+                "EngineVersion": "2.8.24", 
+                "NodeSnapshots": [
+                    {
+                        "CacheSize": "4 MB", 
+                        "SnapshotCreateTime": {
+                            "hour": 15, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 16, 
+                            "microsecond": 0, 
+                            "year": 2016, 
+                            "day": 4, 
+                            "minute": 53
+                        }, 
+                        "CacheNodeId": "0001", 
+                        "CacheNodeCreateTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 45, 
+                            "microsecond": 666000, 
+                            "year": 2016, 
+                            "day": 4, 
+                            "minute": 46
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "Port": 6379, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroupName": "default.redis2.8", 
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheClusterId": "redis-rg-1-003", 
+                "SnapshotRetentionLimit": 0, 
+                "NumCacheNodes": 1, 
+                "SnapshotName": "redis-snapshot-3", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 54, 
+                    "microsecond": 436000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "PreferredAvailabilityZone": "us-west-2c", 
+                "SnapshotStatus": "creating", 
+                "SnapshotSource": "manual", 
+                "SnapshotWindow": "06:00-07:00", 
+                "EngineVersion": "2.8.24", 
+                "NodeSnapshots": [
+                    {
+                        "CacheSize": "4 MB", 
+                        "SnapshotCreateTime": {
+                            "hour": 15, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 36, 
+                            "microsecond": 0, 
+                            "year": 2016, 
+                            "day": 2, 
+                            "minute": 53
+                        }, 
+                        "CacheNodeId": "0001", 
+                        "CacheNodeCreateTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 54, 
+                            "microsecond": 436000, 
+                            "year": 2016, 
+                            "day": 2, 
+                            "minute": 46
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "Port": 6379, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot/elasticache.ListTagsForResource_1.json b/tests/data/placebo/test_elasticache_snapshot/elasticache.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot/elasticache.ListTagsForResource_1.json
@@ -0,0 +1,21 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "e9fd45f3-751d-11e6-ab08-8559232b3224", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e9fd45f3-751d-11e6-ab08-8559232b3224", 
+                "date": "Wed, 07 Sep 2016 17:10:02 GMT", 
+                "content-length": "394", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TagList": [
+            {
+                "Value": "VALUE1", 
+                "Key": "KEY1"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot/iam.ListRoles_1.json b/tests/data/placebo/test_elasticache_snapshot/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot/iam.ListRoles_1.json
@@ -0,0 +1,30 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AAKVUG6oouqUWrxlFukIbjuEpYbnFs07xCfDiKrqFwQXJ5FtAS+E41rwcx8anK0PS96pdOV2t4dX2nmpIR0+E7T7", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAIBRFOWJZSGZJEZVAK", 
+                "CreateDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 56, 
+                    "microsecond": 0, 
+                    "year": 2015, 
+                    "day": 24, 
+                    "minute": 55
+                }, 
+                "RoleName": "BaseIAMRole", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::619193117841:role/BaseIAMRole"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "d4b5d85e-2023-11e6-964c-7b76c530a910"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.DeleteSnapshot_1.json b/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.DeleteSnapshot_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.DeleteSnapshot_1.json
@@ -0,0 +1,70 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Snapshot": {
+            "Engine": "redis", 
+            "CacheParameterGroupName": "default.redis2.8", 
+            "VpcId": "vpc-4a9ff72e", 
+            "CacheClusterId": "redis-rg-1-003", 
+            "SnapshotRetentionLimit": 0, 
+            "NumCacheNodes": 1, 
+            "SnapshotName": "redis-snapshot-3", 
+            "CacheClusterCreateTime": {
+                "hour": 14, 
+                "__class__": "datetime", 
+                "month": 9, 
+                "second": 54, 
+                "microsecond": 436000, 
+                "year": 2016, 
+                "day": 7, 
+                "minute": 46
+            }, 
+            "AutoMinorVersionUpgrade": true, 
+            "PreferredAvailabilityZone": "us-west-2c", 
+            "SnapshotStatus": "deleting", 
+            "SnapshotSource": "manual", 
+            "SnapshotWindow": "06:00-07:00", 
+            "EngineVersion": "2.8.24", 
+            "NodeSnapshots": [
+                {
+                    "CacheSize": "4 MB", 
+                    "SnapshotCreateTime": {
+                        "hour": 15, 
+                        "__class__": "datetime", 
+                        "month": 9, 
+                        "second": 36, 
+                        "microsecond": 0, 
+                        "year": 2016, 
+                        "day": 7, 
+                        "minute": 53
+                    }, 
+                    "CacheNodeId": "0001", 
+                    "CacheNodeCreateTime": {
+                        "hour": 14, 
+                        "__class__": "datetime", 
+                        "month": 9, 
+                        "second": 54, 
+                        "microsecond": 436000, 
+                        "year": 2016, 
+                        "day": 7, 
+                        "minute": 46
+                    }
+                }
+            ], 
+            "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+            "Port": 6379, 
+            "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+            "CacheNodeType": "cache.m3.medium"
+        }, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "8d149125-751f-11e6-bcfa-055709a7c18d", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "8d149125-751f-11e6-bcfa-055709a7c18d", 
+                "date": "Wed, 07 Sep 2016 17:21:44 GMT", 
+                "content-length": "1599", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.DescribeSnapshots_1.json b/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.DescribeSnapshots_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.DescribeSnapshots_1.json
@@ -0,0 +1,182 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "96686044-7513-11e6-8c33-e95c27c07d8a", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "96686044-7513-11e6-8c33-e95c27c07d8a", 
+                "date": "Wed, 07 Sep 2016 15:56:06 GMT", 
+                "content-length": "4479", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "Snapshots": [
+            {
+                "Engine": "redis", 
+                "CacheParameterGroupName": "default.redis2.8", 
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheClusterId": "redis-rg-1-001", 
+                "SnapshotRetentionLimit": 0, 
+                "NumCacheNodes": 1, 
+                "SnapshotName": "redis-snapshot-1", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 29, 
+                    "microsecond": 184000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 41
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "PreferredAvailabilityZone": "us-west-2a", 
+                "SnapshotStatus": "available", 
+                "SnapshotSource": "manual", 
+                "SnapshotWindow": "06:00-07:00", 
+                "EngineVersion": "2.8.24", 
+                "NodeSnapshots": [
+                    {
+                        "CacheSize": "4 MB", 
+                        "SnapshotCreateTime": {
+                            "hour": 15, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 55, 
+                            "microsecond": 0, 
+                            "year": 2016, 
+                            "day": 7, 
+                            "minute": 52
+                        }, 
+                        "CacheNodeId": "0001", 
+                        "CacheNodeCreateTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 29, 
+                            "microsecond": 184000, 
+                            "year": 2016, 
+                            "day": 7, 
+                            "minute": 41
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "Port": 6379, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroupName": "default.redis2.8", 
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheClusterId": "redis-rg-1-002", 
+                "SnapshotRetentionLimit": 2, 
+                "NumCacheNodes": 1, 
+                "SnapshotName": "redis-snapshot-2", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 45, 
+                    "microsecond": 666000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "PreferredAvailabilityZone": "us-west-2b", 
+                "SnapshotStatus": "creating", 
+                "SnapshotSource": "manual", 
+                "SnapshotWindow": "06:00-07:00", 
+                "EngineVersion": "2.8.24", 
+                "NodeSnapshots": [
+                    {
+                        "CacheSize": "4 MB", 
+                        "SnapshotCreateTime": {
+                            "hour": 15, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 16, 
+                            "microsecond": 0, 
+                            "year": 2016, 
+                            "day": 7, 
+                            "minute": 53
+                        }, 
+                        "CacheNodeId": "0001", 
+                        "CacheNodeCreateTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 45, 
+                            "microsecond": 666000, 
+                            "year": 2016, 
+                            "day": 7, 
+                            "minute": 46
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "Port": 6379, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }, 
+            {
+                "Engine": "redis", 
+                "CacheParameterGroupName": "default.redis2.8", 
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheClusterId": "redis-rg-1-003", 
+                "SnapshotRetentionLimit": 0, 
+                "NumCacheNodes": 1, 
+                "SnapshotName": "redis-snapshot-3", 
+                "CacheClusterCreateTime": {
+                    "hour": 14, 
+                    "__class__": "datetime", 
+                    "month": 9, 
+                    "second": 54, 
+                    "microsecond": 436000, 
+                    "year": 2016, 
+                    "day": 7, 
+                    "minute": 46
+                }, 
+                "AutoMinorVersionUpgrade": true, 
+                "PreferredAvailabilityZone": "us-west-2c", 
+                "SnapshotStatus": "creating", 
+                "SnapshotSource": "manual", 
+                "SnapshotWindow": "06:00-07:00", 
+                "EngineVersion": "2.8.24", 
+                "NodeSnapshots": [
+                    {
+                        "CacheSize": "4 MB", 
+                        "SnapshotCreateTime": {
+                            "hour": 15, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 36, 
+                            "microsecond": 0, 
+                            "year": 2016, 
+                            "day": 7, 
+                            "minute": 53
+                        }, 
+                        "CacheNodeId": "0001", 
+                        "CacheNodeCreateTime": {
+                            "hour": 14, 
+                            "__class__": "datetime", 
+                            "month": 9, 
+                            "second": 54, 
+                            "microsecond": 436000, 
+                            "year": 2016, 
+                            "day": 7, 
+                            "minute": 46
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1", 
+                "Port": 6379, 
+                "PreferredMaintenanceWindow": "sun:07:30-sun:08:30", 
+                "CacheNodeType": "cache.m3.medium"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.ListTagsForResource_1.json b/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot_delete/elasticache.ListTagsForResource_1.json
@@ -0,0 +1,21 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "e9fd45f3-751d-11e6-ab08-8559232b3224", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e9fd45f3-751d-11e6-ab08-8559232b3224", 
+                "date": "Wed, 07 Sep 2016 17:10:02 GMT", 
+                "content-length": "394", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TagList": [
+            {
+                "Value": "VALUE1", 
+                "Key": "KEY1"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_snapshot_delete/iam.ListRoles_1.json b/tests/data/placebo/test_elasticache_snapshot_delete/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_snapshot_delete/iam.ListRoles_1.json
@@ -0,0 +1,30 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AAKVUG6oouqUWrxlFukIbjuEpYbnFs07xCfDiKrqFwQXJ5FtAS+E41rwcx8anK0PS96pdOV2t4dX2nmpIR0+E7T7", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAIBRFOWJZSGZJEZVAK", 
+                "CreateDate": {
+                    "hour": 10, 
+                    "__class__": "datetime", 
+                    "month": 11, 
+                    "second": 56, 
+                    "microsecond": 0, 
+                    "year": 2015, 
+                    "day": 24, 
+                    "minute": 55
+                }, 
+                "RoleName": "BaseIAMRole", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::619193117841:role/BaseIAMRole"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "d4b5d85e-2023-11e6-964c-7b76c530a910"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_elasticache_subnet_group/elasticache.DescribeCacheSubnetGroups_1.json b/tests/data/placebo/test_elasticache_subnet_group/elasticache.DescribeCacheSubnetGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_elasticache_subnet_group/elasticache.DescribeCacheSubnetGroups_1.json
@@ -0,0 +1,42 @@
+{
+    "status_code": 200, 
+    "data": {
+        "CacheSubnetGroups": [
+            {
+                "VpcId": "vpc-4a9ff72e", 
+                "CacheSubnetGroupDescription": "An ElastiCache subnet group", 
+                "Subnets": [
+                    {
+                        "SubnetIdentifier": "subnet-5fb47507", 
+                        "SubnetAvailabilityZone": {
+                            "Name": "us-west-2c"
+                        }
+                    }, 
+                    {
+                        "SubnetIdentifier": "subnet-15452171", 
+                        "SubnetAvailabilityZone": {
+                            "Name": "us-west-2a"
+                        }
+                    }, 
+                    {
+                        "SubnetIdentifier": "subnet-63c97615", 
+                        "SubnetAvailabilityZone": {
+                            "Name": "us-west-2b"
+                        }
+                    }
+                ], 
+                "CacheSubnetGroupName": "elasticache-subnet-group-1"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "bcd53202-750a-11e6-bcfa-055709a7c18d", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "bcd53202-750a-11e6-bcfa-055709a7c18d", 
+                "date": "Wed, 07 Sep 2016 14:52:45 GMT", 
+                "content-length": "1309", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_elasticache.py b/tests/test_elasticache.py
new file mode 100644
--- /dev/null
+++ b/tests/test_elasticache.py
@@ -0,0 +1,109 @@
+# Copyright 2016 Capital One Services, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tests.common import BaseTest
+
+
+class TestElastiCacheCluster(BaseTest):
+
+    def test_elasticache_cluster_simple(self):
+        session_factory = self.replay_flight_data('test_elasticache_cluster_simple')
+        p = self.load_policy({
+            'name': 'elasticache-cluster-simple',
+            'resource': 'cache-cluster'},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 4)
+
+    def test_elasticache_cluster_simple_filter(self):
+        session_factory = self.replay_flight_data('test_elasticache_cluster_simple')
+        p = self.load_policy({
+            'name': 'elasticache-cluster-simple-filter',
+            'resource': 'cache-cluster',
+            'filters': [
+                {'type': 'value',
+                 'key': 'Engine',
+                 'value': 'memcached'}]},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+    def test_elasticache_cluster_delete(self):
+        session_factory = self.replay_flight_data('test_elasticache_cluster_delete')
+        p = self.load_policy({
+            'name': 'elasticache-cluster-delete',
+            'resource': 'cache-cluster',
+            'filters': [
+                {'type': 'value',
+                 'key': 'Engine',
+                 'value': 'memcached'}],
+            'actions': [
+                {'type': 'delete'}]},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+    def test_elasticache_cluster_snapshot(self):
+        session_factory = self.replay_flight_data('test_elasticache_cluster_snapshot')
+        p = self.load_policy({
+            'name': 'elasticache-cluster-snapshot',
+            'resource': 'cache-cluster',
+            'actions': [{'type': 'snapshot'}]},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 4)
+
+
+class TestElastiCacheSubnetGroup(BaseTest):
+
+    def test_elasticache_subnet_group(self):
+        session_factory = self.replay_flight_data('test_elasticache_subnet_group')
+        p = self.load_policy({
+            'name': 'elasticache-subnet-group',
+            'resource': 'cache-subnet-group'},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+
+class TestElastiCacheSnapshot(BaseTest):
+
+    def test_elasticache_snapshot(self):
+        session_factory = self.replay_flight_data('test_elasticache_snapshot')
+        p = self.load_policy({
+            'name': 'elasticache-snapshot',
+            'resource': 'cache-snapshot'},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 3)
+
+    def test_elasticache_snapshot_age_filter(self):
+        factory = self.replay_flight_data('test_elasticache_snapshot')
+        p = self.load_policy({
+            'name': 'elasticache-snapshot-age-filter',
+            'resource': 'cache-snapshot',
+            'filters': [{'type': 'age', 'days': 2}]},
+            session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 3)
+
+    def test_elasticache_snapshot_delete(self):
+        factory = self.replay_flight_data('test_elasticache_snapshot_delete')
+        p = self.load_policy({
+            'name': 'elasticache-snapshot-delete',
+            'resource': 'cache-snapshot',
+            'actions': ['delete']},
+            session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 3)
\ No newline at end of file