From 557ad6a411dea25b06234ee9650133ce1a55645e Mon Sep 17 00:00:00 2001
From: Jim Dalton <jim@ere.net>
Date: Mon, 9 Dec 2013 19:38:03 -0800
Subject: [PATCH] Account for instances that have not yet been registered.
 Fixes #5076

---
 cloud/ec2_elb | 55 +++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 49 insertions(+), 6 deletions(-)

diff --git a/cloud/ec2_elb b/cloud/ec2_elb
index e7925a5741f..aab6760d040 100644
--- a/cloud/ec2_elb
+++ b/cloud/ec2_elb
@@ -138,8 +138,14 @@ class ElbManager:
         to report it out-of-service"""
 
         for lb in self.lbs:
-            initial_state = lb.get_instance_health([self.instance_id])[0]
-            lb.deregister_instances([self.instance_id])
+            if wait:
+                initial_state = self._get_instance_health(lb)
+
+            if initial_state and initial_state.state == 'InService':
+                lb.deregister_instances([self.instance_id])
+            else:
+                return
+
             if wait:
                 self._await_elb_instance_state(lb, 'OutOfService', initial_state)
             else:
@@ -151,10 +157,14 @@ class ElbManager:
         """Register the instance for all ELBs and wait for the ELB
         to report the instance in-service"""
         for lb in self.lbs:
-            initial_state = lb.get_instance_health([self.instance_id])[0]
+            if wait:
+                initial_state = self._get_instance_health(lb)
+
             if enable_availability_zone:
                 self._enable_availailability_zone(lb)
+
             lb.register_instances([self.instance_id])
+
             if wait:
                 self._await_elb_instance_state(lb, 'InService', initial_state)
             else:
@@ -164,7 +174,7 @@ class ElbManager:
 
     def exists(self, lbtest):
         """ Verify that the named ELB actually exists """
-        
+
         found = False
         for lb in self.lbs:
             if lb.name == lbtest:
@@ -191,13 +201,22 @@ class ElbManager:
         lb: load balancer
         awaited_state : state to poll for (string)"""
         while True:
-            instance_state = lb.get_instance_health([self.instance_id])[0]
+            instance_state = self._get_instance_health(lb)
+
+            if not instance_state:
+                msg = ("The instance %s could not be put in service on %s."
+                       " Reason: Invalid Instance")
+                self.module.fail_json(msg=msg % (self.instance_id, lb))
+
             if instance_state.state == awaited_state:
                 # Check the current state agains the initial state, and only set
                 # changed if they are different.
                 if instance_state.state != initial_state.state:
                     self.changed = True
                 break
+            elif self._is_instance_state_pending(instance_state):
+                # If it's pending, we'll skip further checks andd continue waiting
+                pass
             elif (awaited_state == 'InService'
                   and instance_state.reason_code == "Instance"):
                 # If the reason_code for the instance being out of service is
@@ -210,8 +229,32 @@ class ElbManager:
                 self.module.fail_json(msg=msg % (self.instance_id,
                                                  lb,
                                                  instance_state.description))
+            time.sleep(1)
+
+    def _is_instance_state_pending(self, instance_state):
+        """
+        Determines whether the instance_state is "pending", meaning there is
+        an operation under way to bring it in service.
+        """
+        # This is messy, because AWS provides no way to distinguish between
+        # an instance that is is OutOfService because it's pending vs. OutOfService
+        # because it's failing health checks. So we're forced to analyze the
+        # description, which is likely to be brittle.
+        return (instance_state and 'pending' in instance_state.description)
+
+    def _get_instance_health(self, lb):
+        """
+        Check instance health, should return status object or None under
+        certain error conditions.
+        """
+        try:
+            status = lb.get_instance_health([self.instance_id])[0]
+        except boto.exception.BotoServerError, e:
+            if e.error_code == 'InvalidInstance':
+                return None
             else:
-                time.sleep(1)
+                raise
+        return status
 
     def _get_instance_lbs(self, ec2_elbs=None):
         """Returns a list of ELBs attached to self.instance_id