Repository: cloud-custodian/cloud-custodian

Issue:
ASG suspend not working for ASG with 0 instances
Not sure if this is a feature but **suspend** action on ASG does not suspend processes if there is no running instance.
```
(custodian)~/dev/git-repos/CloudCustodian/policies master $ custodian run -c test-asg-suspend-noEC2.yml -s ~/tmp/logs-custodian/ -v --cache-period=0
2017-05-16 12:05:45,074: custodian.cli:DEBUG using default region:us-east-1 from boto
2017-05-16 12:05:46,420: custodian.cache:DEBUG Disabling cache
2017-05-16 12:05:46,421: custodian.commands:DEBUG Loaded file test-asg-suspend-noEC2.yml. Contains 1 policies
2017-05-16 12:05:46,523: custodian.cache:DEBUG Disabling cache
2017-05-16 12:05:46,523: custodian.output:DEBUG Storing output with <DirectoryOutput to dir:~/tmp/logs-custodian/asg-suspend-noEC2>
2017-05-16 12:05:46,524: custodian.policy:DEBUG Running policy asg-suspend-noEC2 resource: asg region:us-east-1 c7n:0.8.24.1
2017-05-16 12:05:54,274: custodian.resources.asg:DEBUG Filtered from 154 to 2 asg
2017-05-16 12:05:54,275: custodian.policy:INFO policy: asg-suspend-noEC2 resource:asg region:us-east-1 count:2 time:7.75
2017-05-16 12:05:54,275: custodian.output:DEBUG metric:ResourceCount Count:2 policy:asg-suspend-noec2 restype:asg scope:policy
2017-05-16 12:05:54,277: custodian.actions:DEBUG Filtered from 2 to 1 asgs with instances
2017-05-16 12:05:57,035: custodian.policy:INFO policy: asg-suspend-noEC2 action: suspend resources: 2 execution_time: 2.76
```
See the 2nd line from the bottom - _Filtered from 2 to 1 asgs with instances_.

If the ASG complies with policy filters it should be suspended regardless the number of instances.


Discussion:
sounds good, its a bit of a corner case but totally valid.

Resolving patch:
diff --git a/c7n/resources/asg.py b/c7n/resources/asg.py
--- a/c7n/resources/asg.py
+++ b/c7n/resources/asg.py
@@ -1149,10 +1149,6 @@ class Suspend(Action):
     ASG_PROCESSES = set(ASG_PROCESSES)
 
     def process(self, asgs):
-        original_count = len(asgs)
-        asgs = [a for a in asgs if a['Instances']]
-        self.log.debug("Filtered from %d to %d asgs with instances" % (
-            original_count, len(asgs)))
         with self.executor_factory(max_workers=3) as w:
             list(w.map(self.process_asg, asgs))
 


Test patch:
diff --git a/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_1.json b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_1.json
@@ -0,0 +1,53 @@
+{
+    "status_code": 200, 
+    "data": {
+        "AutoScalingGroups": [
+            {
+                "AutoScalingGroupARN": "arn:aws:autoscaling:us-east-1:644160558196:autoScalingGroup:85aab8d6-1a95-4be3-a835-093b835ba215:autoScalingGroupName/zero-instances", 
+                "TargetGroupARNs": [], 
+                "SuspendedProcesses": [], 
+                "DesiredCapacity": 0, 
+                "Tags": [], 
+                "EnabledMetrics": [], 
+                "LoadBalancerNames": [], 
+                "AutoScalingGroupName": "zero-instances", 
+                "DefaultCooldown": 300, 
+                "MinSize": 0, 
+                "Instances": [], 
+                "MaxSize": 0, 
+                "VPCZoneIdentifier": "subnet-3a334610", 
+                "HealthCheckGracePeriod": 300, 
+                "TerminationPolicies": [
+                    "Default"
+                ], 
+                "LaunchConfigurationName": "scot-test2", 
+                "CreatedTime": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 17, 
+                    "microsecond": 577000, 
+                    "year": 2017, 
+                    "day": 20, 
+                    "minute": 16
+                }, 
+                "AvailabilityZones": [
+                    "us-east-1d"
+                ], 
+                "HealthCheckType": "EC2", 
+                "NewInstancesProtectedFromScaleIn": false
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "b249b991-3f32-11e7-8d2b-19c8d3d1f836", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b249b991-3f32-11e7-8d2b-19c8d3d1f836", 
+                "date": "Mon, 22 May 2017 21:07:42 GMT", 
+                "content-length": "1516", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_2.json b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_2.json
@@ -0,0 +1,53 @@
+{
+    "status_code": 200, 
+    "data": {
+        "AutoScalingGroups": [
+            {
+                "AutoScalingGroupARN": "arn:aws:autoscaling:us-east-1:644160558196:autoScalingGroup:85aab8d6-1a95-4be3-a835-093b835ba215:autoScalingGroupName/zero-instances", 
+                "TargetGroupARNs": [], 
+                "SuspendedProcesses": [], 
+                "DesiredCapacity": 0, 
+                "Tags": [], 
+                "EnabledMetrics": [], 
+                "LoadBalancerNames": [], 
+                "AutoScalingGroupName": "zero-instances", 
+                "DefaultCooldown": 300, 
+                "MinSize": 0, 
+                "Instances": [], 
+                "MaxSize": 0, 
+                "VPCZoneIdentifier": "subnet-3a334610", 
+                "HealthCheckGracePeriod": 300, 
+                "TerminationPolicies": [
+                    "Default"
+                ], 
+                "LaunchConfigurationName": "scot-test2", 
+                "CreatedTime": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 17, 
+                    "microsecond": 577000, 
+                    "year": 2017, 
+                    "day": 20, 
+                    "minute": 16
+                }, 
+                "AvailabilityZones": [
+                    "us-east-1d"
+                ], 
+                "HealthCheckType": "EC2", 
+                "NewInstancesProtectedFromScaleIn": false
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "b288be23-3f32-11e7-baa0-5b5233c75ee3", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b288be23-3f32-11e7-baa0-5b5233c75ee3", 
+                "date": "Mon, 22 May 2017 21:07:42 GMT", 
+                "content-length": "1516", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_3.json b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.DescribeAutoScalingGroups_3.json
@@ -0,0 +1,87 @@
+{
+    "status_code": 200, 
+    "data": {
+        "AutoScalingGroups": [
+            {
+                "AutoScalingGroupARN": "arn:aws:autoscaling:us-east-1:644160558196:autoScalingGroup:85aab8d6-1a95-4be3-a835-093b835ba215:autoScalingGroupName/zero-instances", 
+                "TargetGroupARNs": [], 
+                "SuspendedProcesses": [
+                    {
+                        "ProcessName": "Terminate", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "AZRebalance", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "ReplaceUnhealthy", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "Launch", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "ScheduledActions", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "AddToLoadBalancer", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "HealthCheck", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }, 
+                    {
+                        "ProcessName": "AlarmNotification", 
+                        "SuspensionReason": "User suspended at 2017-05-22T21:07:43Z"
+                    }
+                ], 
+                "DesiredCapacity": 0, 
+                "Tags": [], 
+                "EnabledMetrics": [], 
+                "LoadBalancerNames": [], 
+                "AutoScalingGroupName": "zero-instances", 
+                "DefaultCooldown": 300, 
+                "MinSize": 0, 
+                "Instances": [], 
+                "MaxSize": 0, 
+                "VPCZoneIdentifier": "subnet-3a334610", 
+                "HealthCheckGracePeriod": 300, 
+                "TerminationPolicies": [
+                    "Default"
+                ], 
+                "LaunchConfigurationName": "scot-test2", 
+                "CreatedTime": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 5, 
+                    "second": 17, 
+                    "microsecond": 577000, 
+                    "year": 2017, 
+                    "day": 20, 
+                    "minute": 16
+                }, 
+                "AvailabilityZones": [
+                    "us-east-1d"
+                ], 
+                "HealthCheckType": "EC2", 
+                "NewInstancesProtectedFromScaleIn": false
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "b2ecd76b-3f32-11e7-8d2b-19c8d3d1f836", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b2ecd76b-3f32-11e7-8d2b-19c8d3d1f836", 
+                "vary": "Accept-Encoding", 
+                "content-length": "2984", 
+                "content-type": "text/xml", 
+                "date": "Mon, 22 May 2017 21:07:43 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.SuspendProcesses_1.json b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.SuspendProcesses_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_asg_suspend_when_no_instances/autoscaling.SuspendProcesses_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "b2c48e0b-3f32-11e7-916c-5f9db666e4eb", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b2c48e0b-3f32-11e7-916c-5f9db666e4eb", 
+                "date": "Mon, 22 May 2017 21:07:43 GMT", 
+                "content-length": "219", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_asg.py b/tests/test_asg.py
--- a/tests/test_asg.py
+++ b/tests/test_asg.py
@@ -265,6 +265,31 @@ def test_asg_suspend(self):
                 'AutoScalingGroups'].pop()
         self.assertTrue(result['SuspendedProcesses'])
 
+    def test_asg_suspend_when_no_instances(self):
+        factory = self.replay_flight_data('test_asg_suspend_when_no_instances')
+        client = factory().client('autoscaling')
+
+        # Ensure we have a non-suspended ASG with no instances
+        name = 'zero-instances'
+        result = client.describe_auto_scaling_groups(
+            AutoScalingGroupNames=[name])['AutoScalingGroups'].pop()
+        self.assertEqual(len(result['SuspendedProcesses']), 0)
+        self.assertEqual(len(result['Instances']), 0)
+
+        # Run policy and verify suspend occurs
+        p = self.load_policy({
+            'name': 'asg-suspend',
+            'resource': 'asg',
+            'filters': [
+                {'AutoScalingGroupName': name}],
+            'actions': ['suspend'],
+            }, session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        result = client.describe_auto_scaling_groups(
+            AutoScalingGroupNames=[name])['AutoScalingGroups'].pop()
+        self.assertTrue(result['SuspendedProcesses'])
+
     def test_asg_resume(self):
         factory = self.replay_flight_data('test_asg_resume')
         p = self.load_policy({