Repository: cloud-custodian/cloud-custodian

Issue:
Add Redshift Snapshot resource
Add  Redshift Snapshot resource. Functionality to match the RDS DB Instance Snapshot (`rds-snapshot`) resources where appropriate.
Do **NOT** address tagging functionality to start with.



Discussion:

Resolving patch:
diff --git a/c7n/resources/redshift.py b/c7n/resources/redshift.py
--- a/c7n/resources/redshift.py
+++ b/c7n/resources/redshift.py
@@ -17,7 +17,8 @@
 from concurrent.futures import as_completed
 
 from c7n.actions import ActionRegistry, BaseAction
-from c7n.filters import FilterRegistry, ValueFilter, DefaultVpcBase
+from c7n.filters import (
+    FilterRegistry, ValueFilter, DefaultVpcBase, AgeFilter, OPERATORS)
 
 from c7n.manager import resources
 from c7n.query import QueryResourceManager
@@ -36,6 +37,7 @@ class Redshift(QueryResourceManager):
     filter_registry = filters
     action_registry = actions
 
+
 @filters.register('default-vpc')
 class DefaultVpc(DefaultVpcBase):
     """ Matches if an redshift database is in the default vpc
@@ -44,7 +46,9 @@ class DefaultVpc(DefaultVpcBase):
     schema = type_schema('default-vpc')
 
     def __call__(self, redshift):
-        return redshift.get('VpcId') and self.match(redshift.get('VpcId')) or False
+        return (redshift.get('VpcId') and
+                self.match(redshift.get('VpcId')) or False)
+
 
 @filters.register('param')
 class Parameter(ValueFilter):
@@ -52,9 +56,9 @@ class Parameter(ValueFilter):
     schema = type_schema('param', rinherit=ValueFilter.schema)
     group_params = ()
 
-    def process(self, resources, event=None):
+    def process(self, clusters, event=None):
         groups = {}
-        for r in resources:
+        for r in clusters:
             for pg in r['ClusterParameterGroups']:
                 groups.setdefault(pg['ParameterGroupName'], []).append(
                     r['ClusterIdentifier'])
@@ -76,7 +80,7 @@ def get_params(group_name):
             group_names = groups.keys()
             self.group_params = dict(
                 zip(group_names, w.map(get_params, group_names)))
-        return super(Parameter, self).process(resources, event)
+        return super(Parameter, self).process(clusters, event)
 
     def __call__(self, db):
         params = {}
@@ -91,26 +95,149 @@ class Delete(BaseAction):
     schema = type_schema(
         'delete', **{'skip-snapshot': {'type': 'boolean'}})
 
-    def process(self, resources):
-        self.skip = self.data.get('skip-snapshot', False)
+    def process(self, clusters):
         with self.executor_factory(max_workers=2) as w:
             futures = []
-            for db_set in chunks(resources, size=5):
+            for db_set in chunks(clusters, size=5):
                 futures.append(
                     w.submit(self.process_db_set, db_set))
                 for f in as_completed(futures):
                     if f.exception():
                         self.log.error(
-                            "Exception deleting redshift set \n %s" % (
-                                f.exception()))
+                            "Exception deleting redshift set \n %s",
+                            f.exception())
 
     def process_db_set(self, db_set):
+        skip = self.data.get('skip-snapshot', False)
         c = local_session(self.manager.session_factory).client('redshift')
         for db in db_set:
             params = {'ClusterIdentifier': db['ClusterIdentifier']}
-            if self.skip:
+            if skip:
                 params['SkipFinalClusterSnapshot'] = True
             else:
                 params['FinalClusterSnapshotIdentifier'] = snapshot_identifier(
                     'Final', db['ClusterIdentifier'])
             c.delete_cluster(**params)
+
+
+@actions.register('retention')
+class RetentionWindow(BaseAction):
+
+    date_attribute = 'AutomatedSnapshotRetentionPeriod'
+    schema = type_schema(
+        'retention',
+        **{'days': {'type': 'number'}})
+
+    def process(self, clusters):
+        with self.executor_factory(max_workers=2) as w:
+            futures = []
+            for cluster in clusters:
+                futures.append(w.submit(
+                    self.process_snapshot_retention,
+                    cluster))
+                for f in as_completed(futures):
+                    if f.exception():
+                        self.log.error(
+                            "Exception setting Redshift retention  \n %s",
+                            f.exception())
+
+    def process_snapshot_retention(self, cluster):
+        current_retention = int(cluster.get(self.date_attribute, 0))
+        new_retention = self.data['days']
+
+        if current_retention < new_retention:
+            self.set_retention_window(
+                cluster,
+                max(current_retention, new_retention))
+            return cluster
+
+    def set_retention_window(self, cluster, retention):
+        c = local_session(self.manager.session_factory).client('redshift')
+        c.modify_cluster(
+            ClusterIdentifier=cluster['ClusterIdentifier'],
+            AutomatedSnapshotRetentionPeriod=retention)
+
+
+@actions.register('snapshot')
+class Snapshot(BaseAction):
+
+    schema = type_schema('snapshot')
+
+    def process(self, clusters):
+        with self.executor_factory(max_workers=3) as w:
+            futures = []
+            for cluster in clusters:
+                futures.append(w.submit(
+                    self.process_cluster_snapshot,
+                    cluster))
+                for f in as_completed(futures):
+                    if f.exception():
+                        self.log.error(
+                            "Exception creating Redshift snapshot  \n %s",
+                            f.exception())
+        return clusters
+
+    def process_cluster_snapshot(self, cluster):
+        c = local_session(self.manager.session_factory).client('redshift')
+        c.create_cluster_snapshot(
+            SnapshotIdentifier=snapshot_identifier(
+                'Backup',
+                cluster['ClusterIdentifier']),
+            ClusterIdentifier=cluster['ClusterIdentifier'])
+
+
+@resources.register('redshift-snapshot')
+class RedshiftSnapshot(QueryResourceManager):
+    """Resource manager for Redshift snapshots.
+    """
+
+    class Meta(object):
+
+        service = 'redshift'
+        type = 'redshift-snapshot'
+        enum_spec = ('describe_cluster_snapshots', 'Snapshots', None)
+        name = id = 'SnapshotIdentifier'
+        filter_name = None
+        filter_type = None
+        dimension = None
+        date = 'SnapshotCreateTime'
+
+    resource_type = Meta
+
+    filter_registry = FilterRegistry('redshift-snapshot.filters')
+    action_registry = ActionRegistry('redshift-snapshot.actions')
+
+
+@RedshiftSnapshot.filter_registry.register('age')
+class RedshiftSnapshotAge(AgeFilter):
+
+    schema = type_schema(
+        'age', days={'type': 'number'},
+        op={'type': 'string', 'enum': OPERATORS.keys()})
+
+    date_attribute = 'SnapshotCreateTime'
+
+
+@RedshiftSnapshot.action_registry.register('delete')
+class RedshiftSnapshotDelete(BaseAction):
+
+    def process(self, snapshots):
+        log.info("Deleting %d Redshift 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('redshift')
+        for s in snapshots_set:
+            c.delete_cluster_snapshot(
+                SnapshotIdentifier=s['SnapshotIdentifier'],
+                SnapshotClusterIdentifier=s['ClusterIdentifier'])


Test patch:
diff --git a/tests/data/placebo/test_redshift_retention/redshift.DescribeClusters_1.json b/tests/data/placebo/test_redshift_retention/redshift.DescribeClusters_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_retention/redshift.DescribeClusters_1.json
@@ -0,0 +1,151 @@
+{
+    "status_code": 200,
+    "data": {
+        "Clusters": [
+            {
+                "PubliclyAccessible": false,
+                "MasterUsername": "admin",
+                "VpcSecurityGroups": [
+                    {
+                        "Status": "active",
+                        "VpcSecurityGroupId": "sg-123456"
+                    }
+                ],
+                "ClusterPublicKey": "ssh-rsa ABCD Amazon-Redshift\n",
+                "NumberOfNodes": 1,
+                "PendingModifiedValues": {},
+                "VpcId": "vpc-12345",
+                "ClusterVersion": "1.0",
+                "Tags": [
+                    {
+                        "Value": "V1",
+                        "Key": "K1"
+                    },
+                    {
+                        "Value": "V2",
+                        "Key": "K2"
+                    },
+                    {
+                        "Value": "V3 ",
+                        "Key": "K3"
+                    }
+                ],
+                "AutomatedSnapshotRetentionPeriod": 1,
+                "ClusterParameterGroups": [
+                    {
+                        "ParameterGroupName": "default.redshift-1.0",
+                        "ParameterApplyStatus": "in-sync"
+                    }
+                ],
+                "DBName": "devdb",
+                "PreferredMaintenanceWindow": "tue:10:30-tue:11:00",
+                "Endpoint": {
+                    "Port": 5439,
+                    "Address": "bbb.ccc.us-east-1.redshift.amazonaws.com"
+                },
+                "IamRoles": [],
+                "AllowVersionUpgrade": true,
+                "ClusterCreateTime": {
+                    "hour": 18,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 23,
+                    "microsecond": 413000,
+                    "year": 2016,
+                    "day": 12,
+                    "minute": 56
+                },
+                "ClusterSubnetGroupName": "b-subnet-group",
+                "ClusterSecurityGroups": [],
+                "ClusterIdentifier": "bbb",
+                "ClusterNodes": [
+                    {
+                        "NodeRole": "SHARED",
+                        "PrivateIPAddress": "10.1.1.1",
+                        "PublicIPAddress": "1.1.1."
+                    }
+                ],
+                "AvailabilityZone": "us-east-1b",
+                "NodeType": "dc1.large",
+                "Encrypted": true,
+                "ClusterRevisionNumber": "1088",
+                "KmsKeyId": "arn:aws:kms:us-east-1:12345:key/k1",
+                "ClusterStatus": "available"
+            },
+            {
+                "PubliclyAccessible": false,
+                "MasterUsername": "admin",
+                "VpcSecurityGroups": [
+                    {
+                        "Status": "active",
+                        "VpcSecurityGroupId": "sg-54321"
+                    }
+                ],
+                "ClusterPublicKey": "ssh-rsa DCBA Amazon-Redshift\n",
+                "NumberOfNodes": 1,
+                "PendingModifiedValues": {},
+                "VpcId": "vpc-54321",
+                "ClusterVersion": "1.0",
+                "Tags": [
+                    {
+                        "Value": "V9",
+                        "Key": "K9"
+                    },
+                    {
+                        "Value": "V8",
+                        "Key": "K8"
+                    },
+                    {
+                        "Value": "V7",
+                        "Key": "K7"
+                    }
+                ],
+                "AutomatedSnapshotRetentionPeriod": 1,
+                "ClusterParameterGroups": [
+                    {
+                        "ParameterGroupName": "default.redshift-1.0",
+                        "ParameterApplyStatus": "in-sync"
+                    }
+                ],
+                "DBName": "dev",
+                "PreferredMaintenanceWindow": "thu:03:00-thu:03:30",
+                "Endpoint": {
+                    "Port": 5439,
+                    "Address": "aaa.ccc.us-east-1.redshift.amazonaws.com"
+                },
+                "IamRoles": [],
+                "AllowVersionUpgrade": true,
+                "ClusterCreateTime": {
+                    "hour": 13,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 26,
+                    "microsecond": 191000,
+                    "year": 2016,
+                    "day": 23,
+                    "minute": 11
+                },
+                "ClusterSubnetGroupName": "a-subnet-group",
+                "ClusterSecurityGroups": [],
+                "ClusterIdentifier": "aaa",
+                "ClusterNodes": [
+                    {
+                        "NodeRole": "SHARED",
+                        "PrivateIPAddress": "10.2.2.2",
+                        "PublicIPAddress": "2.2.2.2"
+                    }
+                ],
+                "AvailabilityZone": "us-east-1b",
+                "NodeType": "dc1.large",
+                "Encrypted": true,
+                "ClusterRevisionNumber": "1088",
+                "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k2",
+                "ClusterStatus": "available"
+            }
+        ],
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "23e19206-6934-11e6-997a-179e1a22104f"
+        }
+    }
+}
diff --git a/tests/data/placebo/test_redshift_retention/redshift.ModifyCluster_1.json b/tests/data/placebo/test_redshift_retention/redshift.ModifyCluster_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_retention/redshift.ModifyCluster_1.json
@@ -0,0 +1,79 @@
+{
+    "status_code": 200,
+    "data": {
+        "Cluster":             {
+                        "PubliclyAccessible": false,
+                        "MasterUsername": "admin",
+                        "VpcSecurityGroups": [
+                            {
+                                "Status": "active",
+                                "VpcSecurityGroupId": "sg-54321"
+                            }
+                        ],
+                        "ClusterPublicKey": "ssh-rsa DCBA Amazon-Redshift\n",
+                        "NumberOfNodes": 1,
+                        "PendingModifiedValues": {},
+                        "VpcId": "vpc-54321",
+                        "ClusterVersion": "1.0",
+                        "Tags": [
+                            {
+                                "Value": "V9",
+                                "Key": "K9"
+                            },
+                            {
+                                "Value": "V8",
+                                "Key": "K8"
+                            },
+                            {
+                                "Value": "V7",
+                                "Key": "K7"
+                            }
+                        ],
+                        "AutomatedSnapshotRetentionPeriod": 21,
+                        "ClusterParameterGroups": [
+                            {
+                                "ParameterGroupName": "default.redshift-1.0",
+                                "ParameterApplyStatus": "in-sync"
+                            }
+                        ],
+                        "DBName": "dev",
+                        "PreferredMaintenanceWindow": "thu:03:00-thu:03:30",
+                        "Endpoint": {
+                            "Port": 5439,
+                            "Address": "aaa.ccc.us-east-1.redshift.amazonaws.com"
+                        },
+                        "IamRoles": [],
+                        "AllowVersionUpgrade": true,
+                        "ClusterCreateTime": {
+                            "hour": 13,
+                            "__class__": "datetime",
+                            "month": 8,
+                            "second": 26,
+                            "microsecond": 191000,
+                            "year": 2016,
+                            "day": 23,
+                            "minute": 11
+                        },
+                        "ClusterSubnetGroupName": "a-subnet-group",
+                        "ClusterSecurityGroups": [],
+                        "ClusterIdentifier": "aaa",
+                        "ClusterNodes": [
+                            {
+                                "NodeRole": "SHARED",
+                                "PrivateIPAddress": "10.2.2.2",
+                                "PublicIPAddress": "2.2.2.2"
+                            }
+                        ],
+                        "AvailabilityZone": "us-east-1b",
+                        "NodeType": "dc1.large",
+                        "Encrypted": true,
+                        "ClusterRevisionNumber": "1088",
+                        "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k2",
+                        "ClusterStatus": "available"
+                    },
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "c1234a50-6934-11e6-bd92-af313dc1b009"
+        }
+    }
+}
diff --git a/tests/data/placebo/test_redshift_snapshot/redshift.CreateClusterSnapshot_1.json b/tests/data/placebo/test_redshift_snapshot/redshift.CreateClusterSnapshot_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_snapshot/redshift.CreateClusterSnapshot_1.json
@@ -0,0 +1,54 @@
+{
+    "status_code": 200,
+    "data": {
+        "Snapshot": {
+            "MasterUsername": "admin",
+            "OwnerAccount": "54321",
+            "CurrentBackupRateInMegaBytesPerSecond": 0.0,
+            "ActualIncrementalBackupSizeInMegaBytes": -1.0,
+            "NumberOfNodes": 1,
+            "Status": "creating",
+            "VpcId": "vpc-54321",
+            "ClusterVersion": "1.0",
+            "Tags": [],
+            "EstimatedSecondsToCompletion": -1,
+            "TotalBackupSizeInMegaBytes": -1.0,
+            "DBName": "dev",
+            "BackupProgressInMegaBytes": 0.0,
+            "ClusterCreateTime": {
+                "hour": 13,
+                "__class__": "datetime",
+                "month": 8,
+                "second": 26,
+                "microsecond": 191000,
+                "year": 2016,
+                "day": 23,
+                "minute": 11
+            },
+            "EncryptedWithHSM": false,
+            "ClusterIdentifier": "aaa",
+            "SnapshotCreateTime": {
+                "hour": 13,
+                "__class__": "datetime",
+                "month": 8,
+                "second": 24,
+                "microsecond": 707000,
+                "year": 2016,
+                "day": 23,
+                "minute": 21
+            },
+            "AvailabilityZone": "us-east-1b",
+            "NodeType": "dc1.large",
+            "Encrypted": true,
+            "ElapsedTimeInSeconds": 0,
+            "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k2", 
+            "SnapshotType": "manual",
+            "Port": 5439,
+            "SnapshotIdentifier": "aaa-backup-1"
+        },
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "7d8a9a90-6934-11e6-a715-c351be468770"
+        }
+    }
+}
diff --git a/tests/data/placebo/test_redshift_snapshot/redshift.DescribeClusters_1.json b/tests/data/placebo/test_redshift_snapshot/redshift.DescribeClusters_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_snapshot/redshift.DescribeClusters_1.json
@@ -0,0 +1,151 @@
+{
+    "status_code": 200,
+    "data": {
+        "Clusters": [
+            {
+                "PubliclyAccessible": false,
+                "MasterUsername": "admin",
+                "VpcSecurityGroups": [
+                    {
+                        "Status": "active",
+                        "VpcSecurityGroupId": "sg-123456"
+                    }
+                ],
+                "ClusterPublicKey": "ssh-rsa ABCD Amazon-Redshift\n",
+                "NumberOfNodes": 1,
+                "PendingModifiedValues": {},
+                "VpcId": "vpc-12345",
+                "ClusterVersion": "1.0",
+                "Tags": [
+                    {
+                        "Value": "V1",
+                        "Key": "K1"
+                    },
+                    {
+                        "Value": "V2",
+                        "Key": "K2"
+                    },
+                    {
+                        "Value": "V3 ",
+                        "Key": "K3"
+                    }
+                ],
+                "AutomatedSnapshotRetentionPeriod": 1,
+                "ClusterParameterGroups": [
+                    {
+                        "ParameterGroupName": "default.redshift-1.0",
+                        "ParameterApplyStatus": "in-sync"
+                    }
+                ],
+                "DBName": "devdb",
+                "PreferredMaintenanceWindow": "tue:10:30-tue:11:00",
+                "Endpoint": {
+                    "Port": 5439,
+                    "Address": "bbb.ccc.us-east-1.redshift.amazonaws.com"
+                },
+                "IamRoles": [],
+                "AllowVersionUpgrade": true,
+                "ClusterCreateTime": {
+                    "hour": 18,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 23,
+                    "microsecond": 413000,
+                    "year": 2016,
+                    "day": 12,
+                    "minute": 56
+                },
+                "ClusterSubnetGroupName": "b-subnet-group",
+                "ClusterSecurityGroups": [],
+                "ClusterIdentifier": "bbb",
+                "ClusterNodes": [
+                    {
+                        "NodeRole": "SHARED",
+                        "PrivateIPAddress": "10.1.1.1",
+                        "PublicIPAddress": "1.1.1."
+                    }
+                ],
+                "AvailabilityZone": "us-east-1b",
+                "NodeType": "dc1.large",
+                "Encrypted": true,
+                "ClusterRevisionNumber": "1088",
+                "KmsKeyId": "arn:aws:kms:us-east-1:12345:key/k1",
+                "ClusterStatus": "available"
+            },
+            {
+                "PubliclyAccessible": false,
+                "MasterUsername": "admin",
+                "VpcSecurityGroups": [
+                    {
+                        "Status": "active",
+                        "VpcSecurityGroupId": "sg-54321"
+                    }
+                ],
+                "ClusterPublicKey": "ssh-rsa DCBA Amazon-Redshift\n",
+                "NumberOfNodes": 1,
+                "PendingModifiedValues": {},
+                "VpcId": "vpc-54321",
+                "ClusterVersion": "1.0",
+                "Tags": [
+                    {
+                        "Value": "V9",
+                        "Key": "K9"
+                    },
+                    {
+                        "Value": "V8",
+                        "Key": "K8"
+                    },
+                    {
+                        "Value": "V7",
+                        "Key": "K7"
+                    }
+                ],
+                "AutomatedSnapshotRetentionPeriod": 1,
+                "ClusterParameterGroups": [
+                    {
+                        "ParameterGroupName": "default.redshift-1.0",
+                        "ParameterApplyStatus": "in-sync"
+                    }
+                ],
+                "DBName": "dev",
+                "PreferredMaintenanceWindow": "thu:03:00-thu:03:30",
+                "Endpoint": {
+                    "Port": 5439,
+                    "Address": "aaa.ccc.us-east-1.redshift.amazonaws.com"
+                },
+                "IamRoles": [],
+                "AllowVersionUpgrade": true,
+                "ClusterCreateTime": {
+                    "hour": 13,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 26,
+                    "microsecond": 191000,
+                    "year": 2016,
+                    "day": 23,
+                    "minute": 11
+                },
+                "ClusterSubnetGroupName": "a-subnet-group",
+                "ClusterSecurityGroups": [],
+                "ClusterIdentifier": "aaa",
+                "ClusterNodes": [
+                    {
+                        "NodeRole": "SHARED",
+                        "PrivateIPAddress": "10.2.2.2",
+                        "PublicIPAddress": "2.2.2.2"
+                    }
+                ],
+                "AvailabilityZone": "us-east-1b",
+                "NodeType": "dc1.large",
+                "Encrypted": true,
+                "ClusterRevisionNumber": "1088",
+                "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k2",
+                "ClusterStatus": "available"
+            }
+        ],
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "23e19206-6934-11e6-997a-179e1a22104f"
+        }
+    }
+}
diff --git a/tests/data/placebo/test_redshift_snapshot_delete/redshift.DeleteClusterSnapshot_1.json b/tests/data/placebo/test_redshift_snapshot_delete/redshift.DeleteClusterSnapshot_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_snapshot_delete/redshift.DeleteClusterSnapshot_1.json
@@ -0,0 +1,54 @@
+{
+    "status_code": 200,
+    "data": {
+        "Snapshot": {
+            "MasterUsername": "user1",
+            "OwnerAccount": "54321",
+            "CurrentBackupRateInMegaBytesPerSecond": 7.4212,
+            "ActualIncrementalBackupSizeInMegaBytes": 4.0,
+            "NumberOfNodes": 1,
+            "Status": "deleted",
+            "VpcId": "vpc-54321",
+            "ClusterVersion": "1.0",
+            "Tags": [],
+            "EstimatedSecondsToCompletion": 0,
+            "TotalBackupSizeInMegaBytes": 12.0,
+            "DBName": "dev",
+            "BackupProgressInMegaBytes": 4.0,
+            "ClusterCreateTime": {
+                "hour": 13,
+                "__class__": "datetime",
+                "month": 8,
+                "second": 26,
+                "microsecond": 191000,
+                "year": 2016,
+                "day": 23,
+                "minute": 11
+            },
+            "EncryptedWithHSM": false,
+            "ClusterIdentifier": "aaa",
+            "SnapshotCreateTime": {
+                "hour": 18,
+                "__class__": "datetime", 
+                "month": 8,
+                "second": 24,
+                "microsecond": 21000,
+                "year": 2016,
+                "day": 23,
+                "minute": 38
+            },
+            "AvailabilityZone": "us-east-1b",
+            "NodeType": "dc1.large",
+            "Encrypted": true,
+            "ElapsedTimeInSeconds": 0,
+            "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k1",
+            "SnapshotType": "manual",
+            "Port": 5439,
+            "SnapshotIdentifier": "aaa-1"
+        },
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "64669fee-6961-11e6-88a8-bde2627b50d4"
+        }
+    }
+}
diff --git a/tests/data/placebo/test_redshift_snapshot_delete/redshift.DescribeClusterSnapshots_1.json b/tests/data/placebo/test_redshift_snapshot_delete/redshift.DescribeClusterSnapshots_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_snapshot_delete/redshift.DescribeClusterSnapshots_1.json
@@ -0,0 +1,106 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "31ada85c-693b-11e6-bea2-6fbdbf1461bf"
+        },
+        "Snapshots": [
+            {
+                "EstimatedSecondsToCompletion": 0,
+                "OwnerAccount": "12345",
+                "CurrentBackupRateInMegaBytesPerSecond": 5.005,
+                "ActualIncrementalBackupSizeInMegaBytes": 5.0,
+                "NumberOfNodes": 1,
+                "Status": "available",
+                "VpcId": "vpc-12345",
+                "ClusterVersion": "1.0",
+                "Tags": [],
+                "MasterUsername": "user1",
+                "TotalBackupSizeInMegaBytes": 12.0,
+                "DBName": "dev",
+                "BackupProgressInMegaBytes": 5.0,
+                "ClusterCreateTime": {
+                    "hour": 14,
+                    "__class__": "datetime",
+                    "month": 6,
+                    "second": 45,
+                    "microsecond": 642000,
+                    "year": 2016,
+                    "day": 20,
+                    "minute": 10
+                },
+                "RestorableNodeTypes": [
+                    "dc1.large"
+                ],
+                "EncryptedWithHSM": false,
+                "ClusterIdentifier": "xxx",
+                "SnapshotCreateTime": {
+                    "hour": 20,
+                    "__class__": "datetime",
+                    "month": 6,
+                    "second": 46,
+                    "microsecond": 372000,
+                    "year": 2016,
+                    "day": 20,
+                    "minute": 41
+                },
+                "AvailabilityZone": "us-east-1a",
+                "NodeType": "dc1.large",
+                "Encrypted": false,
+                "ElapsedTimeInSeconds": 0,
+                "SnapshotType": "manual",
+                "Port": 5439,
+                "SnapshotIdentifier": "xxx-1"
+            },
+            {
+                "MasterUsername": "user2",
+                "OwnerAccount": "54321",
+                "CurrentBackupRateInMegaBytesPerSecond": 7.1839,
+                "ActualIncrementalBackupSizeInMegaBytes": 5.0,
+                "NumberOfNodes": 1,
+                "Status": "available",
+                "VpcId": "vpc-54321",
+                "ClusterVersion": "1.0",
+                "Tags": [],
+                "EstimatedSecondsToCompletion": 0,
+                "TotalBackupSizeInMegaBytes": 12.0,
+                "DBName": "dev",
+                "BackupProgressInMegaBytes": 5.0,
+                "ClusterCreateTime": {
+                    "hour": 13,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 26,
+                    "microsecond": 191000,
+                    "year": 2016,
+                    "day": 13,
+                    "minute": 11
+                },
+                "RestorableNodeTypes": [
+                    "dc1.large"
+                ],
+                "EncryptedWithHSM": false,
+                "ClusterIdentifier": "yyy",
+                "SnapshotCreateTime": {
+                    "hour": 13,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 32,
+                    "microsecond": 603000,
+                    "year": 2016,
+                    "day": 13,
+                    "minute": 21
+                },
+                "AvailabilityZone": "us-east-1b",
+                "NodeType": "dc1.large",
+                "Encrypted": true,
+                "ElapsedTimeInSeconds": 0,
+                "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k1",
+                "SnapshotType": "manual",
+                "Port": 5439,
+                "SnapshotIdentifier": "yyy-1"
+            }
+        ]
+    }
+}
diff --git a/tests/data/placebo/test_redshift_snapshot_simple/redshift.DescribeClusterSnapshots_1.json b/tests/data/placebo/test_redshift_snapshot_simple/redshift.DescribeClusterSnapshots_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_snapshot_simple/redshift.DescribeClusterSnapshots_1.json
@@ -0,0 +1,106 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RequestId": "31ada85c-693b-11e6-bea2-6fbdbf1461bf"
+        },
+        "Snapshots": [
+            {
+                "EstimatedSecondsToCompletion": 0,
+                "OwnerAccount": "12345",
+                "CurrentBackupRateInMegaBytesPerSecond": 5.005,
+                "ActualIncrementalBackupSizeInMegaBytes": 5.0,
+                "NumberOfNodes": 1,
+                "Status": "available",
+                "VpcId": "vpc-12345",
+                "ClusterVersion": "1.0",
+                "Tags": [],
+                "MasterUsername": "user1",
+                "TotalBackupSizeInMegaBytes": 12.0,
+                "DBName": "dev",
+                "BackupProgressInMegaBytes": 5.0,
+                "ClusterCreateTime": {
+                    "hour": 14,
+                    "__class__": "datetime",
+                    "month": 6,
+                    "second": 45,
+                    "microsecond": 642000,
+                    "year": 2016,
+                    "day": 20,
+                    "minute": 10
+                },
+                "RestorableNodeTypes": [
+                    "dc1.large"
+                ],
+                "EncryptedWithHSM": false,
+                "ClusterIdentifier": "xxx",
+                "SnapshotCreateTime": {
+                    "hour": 20,
+                    "__class__": "datetime",
+                    "month": 6,
+                    "second": 46,
+                    "microsecond": 372000,
+                    "year": 2016,
+                    "day": 20,
+                    "minute": 41
+                },
+                "AvailabilityZone": "us-east-1a",
+                "NodeType": "dc1.large",
+                "Encrypted": false,
+                "ElapsedTimeInSeconds": 0,
+                "SnapshotType": "manual",
+                "Port": 5439,
+                "SnapshotIdentifier": "xxx-1"
+            },
+            {
+                "MasterUsername": "user2",
+                "OwnerAccount": "54321",
+                "CurrentBackupRateInMegaBytesPerSecond": 7.1839,
+                "ActualIncrementalBackupSizeInMegaBytes": 5.0,
+                "NumberOfNodes": 1,
+                "Status": "available",
+                "VpcId": "vpc-54321",
+                "ClusterVersion": "1.0",
+                "Tags": [],
+                "EstimatedSecondsToCompletion": 0,
+                "TotalBackupSizeInMegaBytes": 12.0,
+                "DBName": "dev",
+                "BackupProgressInMegaBytes": 5.0,
+                "ClusterCreateTime": {
+                    "hour": 13,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 26,
+                    "microsecond": 191000,
+                    "year": 2016,
+                    "day": 13,
+                    "minute": 11
+                },
+                "RestorableNodeTypes": [
+                    "dc1.large"
+                ],
+                "EncryptedWithHSM": false,
+                "ClusterIdentifier": "yyy",
+                "SnapshotCreateTime": {
+                    "hour": 13,
+                    "__class__": "datetime",
+                    "month": 8,
+                    "second": 32,
+                    "microsecond": 603000,
+                    "year": 2016,
+                    "day": 13,
+                    "minute": 21
+                },
+                "AvailabilityZone": "us-east-1b",
+                "NodeType": "dc1.large",
+                "Encrypted": true,
+                "ElapsedTimeInSeconds": 0,
+                "KmsKeyId": "arn:aws:kms:us-east-1:54321:key/k1",
+                "SnapshotType": "manual",
+                "Port": 5439,
+                "SnapshotIdentifier": "yyy-1"
+            }
+        ]
+    }
+}
diff --git a/tests/test_redshift.py b/tests/test_redshift.py
--- a/tests/test_redshift.py
+++ b/tests/test_redshift.py
@@ -16,7 +16,7 @@
 
 class TestRedshift(BaseTest):
 
-    def test_query(self):
+    def test_redshift_query(self):
         factory = self.replay_flight_data('test_redshift_query')
         p = self.load_policy({
             'name': 'redshift-query',
@@ -24,7 +24,7 @@ def test_query(self):
         resources = p.run()
         self.assertEqual(resources, [])
 
-    def test_parameter(self):
+    def test_redshift_parameter(self):
         factory = self.replay_flight_data('test_redshift_parameter')
         p = self.load_policy({
             'name': 'redshift-ssl',
@@ -38,7 +38,7 @@ def test_parameter(self):
         resources = p.run()
         self.assertEqual(len(resources), 1)
 
-    def test_delete(self):
+    def test_redshift_delete(self):
         factory = self.replay_flight_data('test_redshift_delete')
         p = self.load_policy({
             'name': 'redshift-ssl',
@@ -63,3 +63,75 @@ def test_redshift_default_vpc(self):
 
         resources = p.run()
         self.assertEqual(len(resources), 1)
+
+    def test_redshift_retention(self):
+        session_factory = self.replay_flight_data('test_redshift_retention')
+        p = self.load_policy({
+            'name': 'redshift-retention',
+            'resource': 'redshift',
+            'filters': [
+                {'type': 'value',
+                 'key': 'ClusterIdentifier',
+                 'value': 'aaa'}],
+            'actions': [{'type': 'retention', 'days': 21}]},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+    def test_redshift_snapshot(self):
+        factory = self.replay_flight_data('test_redshift_snapshot')
+        p = self.load_policy({
+            'name': 'redshift-snapshot',
+            'resource': 'redshift',
+            'filters': [
+                {'ClusterIdentifier': 'aaa'}],
+            'actions': [
+                {'type': 'snapshot'}]},
+            session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+
+class TestRedshiftSnapshot(BaseTest):
+
+    def test_redshift_snapshot_simple(self):
+        session_factory = self.replay_flight_data('test_redshift_snapshot_simple')
+        p = self.load_policy({
+            'name': 'redshift-snapshot-simple',
+            'resource': 'redshift-snapshot'},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 2)
+
+    def test_redshift_snapshot_simple_filter(self):
+        session_factory = self.replay_flight_data('test_redshift_snapshot_simple')
+        p = self.load_policy({
+            'name': 'redshift-snapshot-simple-filter',
+            'resource': 'redshift-snapshot',
+            'filters': [
+                {'type': 'value',
+                 'key': 'Encrypted',
+                 'value': False}]},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
+    def test_redshift_snapshot_age_filter(self):
+        factory = self.replay_flight_data('test_redshift_snapshot_simple')
+        p = self.load_policy({
+            'name': 'redshift-snapshot-age-filter',
+            'resource': 'redshift-snapshot',
+            'filters': [{'type': 'age', 'days': 7}]},
+            session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 2)
+
+    def test_redshift_snapshot_delete(self):
+        factory = self.replay_flight_data('test_redshift_snapshot_delete')
+        p = self.load_policy({
+            'name': 'redshift-snapshot-delete',
+            'resource': 'redshift-snapshot',
+            'actions': ['delete']},
+            session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 2)