Repository: boto/boto

Issue:
add support for ELB ConnectionDraining attribute
Hi,

I'd like boto to support the new connection draining feature of ELB.  this feature is described here:

http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/TerminologyandKeyConcepts.html#conn-drain

Connection draining is an ELB attribute, described here:

http://docs.aws.amazon.com/ElasticLoadBalancing/latest/APIReference/API_ModifyLoadBalancerAttributes.html

I have a working change that adds support for this feature to boto.  I need to test it more and make sure it is correct and then I will associate it with this issue.



Discussion:

Resolving patch:
diff --git a/boto/ec2/elb/__init__.py b/boto/ec2/elb/__init__.py
--- a/boto/ec2/elb/__init__.py
+++ b/boto/ec2/elb/__init__.py
@@ -386,6 +386,7 @@ def modify_lb_attribute(self, load_balancer_name, attribute, value):
 
         * crossZoneLoadBalancing - Boolean (true)
         * accessLog - :py:class:`AccessLogAttribute` instance
+        * connectionDraining - :py:class:`ConnectionDrainingAttribute` instance
 
         :type value: string
         :param value: The new value for the attribute
@@ -415,6 +416,11 @@ def modify_lb_attribute(self, load_balancer_name, attribute, value):
                 value.s3_bucket_prefix
             params['LoadBalancerAttributes.AccessLog.EmitInterval'] = \
                 value.emit_interval
+        elif attribute.lower() == 'connectiondraining':
+            params['LoadBalancerAttributes.ConnectionDraining.Enabled'] = \
+                value.enabled and 'true' or 'false'
+            params['LoadBalancerAttributes.ConnectionDraining.Timeout'] = \
+                value.timeout
         else:
             raise ValueError('InvalidAttribute', attribute)
         return self.get_status('ModifyLoadBalancerAttributes', params,
@@ -445,14 +451,20 @@ def get_lb_attribute(self, load_balancer_name, attribute):
         :type attribute: string
         :param attribute: The attribute you wish to see.
 
+          * accessLog - :py:class:`AccessLogAttribute` instance
           * crossZoneLoadBalancing - Boolean
+          * connectionDraining - :py:class:`ConnectionDrainingAttribute` instance
 
         :rtype: Attribute dependent
         :return: The new value for the attribute
         """
         attributes = self.get_all_lb_attributes(load_balancer_name)
+        if attribute.lower() == 'accesslog':
+            return attributes.access_log
         if attribute.lower() == 'crosszoneloadbalancing':
             return attributes.cross_zone_load_balancing.enabled
+        if attribute.lower() == 'connectiondraining':
+            return attributes.connection_draining
         return None
 
     def register_instances(self, load_balancer_name, instances):
diff --git a/boto/ec2/elb/attributes.py b/boto/ec2/elb/attributes.py
--- a/boto/ec2/elb/attributes.py
+++ b/boto/ec2/elb/attributes.py
@@ -74,6 +74,31 @@ def endElement(self, name, value, connection):
         elif name == 'EmitInterval':
             self.emit_interval = int(value)
 
+class ConnectionDrainingAttribute(object):
+    """
+    Represents the ConnectionDraining segment of ELB attributes.
+    """
+    def __init__(self, connection=None):
+        self.enabled = None
+        self.timeout = None
+
+    def __repr__(self):
+        return 'ConnectionDraining(%s, %s)' % (
+            self.enabled,
+            self.timeout
+        )
+
+    def startElement(self, name, attrs, connection):
+        pass
+
+    def endElement(self, name, value, connection):
+        if name == 'Enabled':
+            if value.lower() == 'true':
+                self.enabled = True
+            else:
+                self.enabled = False
+        elif name == 'Timeout':
+            self.timeout = int(value)
 
 class LbAttributes(object):
     """
@@ -84,17 +109,21 @@ def __init__(self, connection=None):
         self.cross_zone_load_balancing = CrossZoneLoadBalancingAttribute(
           self.connection)
         self.access_log = AccessLogAttribute(self.connection)
+        self.connection_draining = ConnectionDrainingAttribute(self.connection)
 
     def __repr__(self):
-        return 'LbAttributes(%s, %s)' % (
+        return 'LbAttributes(%s, %s, %s)' % (
             repr(self.cross_zone_load_balancing),
-            repr(self.access_log))
+            repr(self.access_log),
+            repr(self.connection_draining))
 
     def startElement(self, name, attrs, connection):
         if name == 'CrossZoneLoadBalancing':
             return self.cross_zone_load_balancing
         if name == 'AccessLog':
             return self.access_log
+        if name == 'ConnectionDraining':
+            return self.connection_draining
 
     def endElement(self, name, value, connection):
         pass


Test patch:
diff --git a/tests/integration/ec2/elb/test_connection.py b/tests/integration/ec2/elb/test_connection.py
--- a/tests/integration/ec2/elb/test_connection.py
+++ b/tests/integration/ec2/elb/test_connection.py
@@ -198,6 +198,48 @@ def test_load_balancer_access_log(self):
             new_attributes.access_log.s3_bucket_prefix)
         self.assertEqual(5, new_attributes.access_log.emit_interval)
 
+    def test_load_balancer_get_attributes(self):
+        attributes = self.balancer.get_attributes()
+        connection_draining = self.conn.get_lb_attribute(self.balancer.name,
+                                                         'ConnectionDraining')
+        self.assertEqual(connection_draining.enabled,
+                         attributes.connection_draining.enabled)
+        self.assertEqual(connection_draining.timeout,
+                         attributes.connection_draining.timeout)
+
+        access_log = self.conn.get_lb_attribute(self.balancer.name,
+                                                'AccessLog')
+        self.assertEqual(access_log.enabled, attributes.access_log.enabled)
+        self.assertEqual(access_log.s3_bucket_name, attributes.access_log.s3_bucket_name)
+        self.assertEqual(access_log.s3_bucket_prefix, attributes.access_log.s3_bucket_prefix)
+        self.assertEqual(access_log.emit_interval, attributes.access_log.emit_interval)
+
+        cross_zone_load_balancing = self.conn.get_lb_attribute(self.balancer.name,
+                                                'CrossZoneLoadBalancing')
+        self.assertEqual(cross_zone_load_balancing,
+                         attributes.cross_zone_load_balancing.enabled)
+
+    def change_and_verify_load_balancer_connection_draining(self, enabled, timeout = None):
+        attributes = self.balancer.get_attributes()
+
+        attributes.connection_draining.enabled = enabled
+        if timeout != None:
+            attributes.connection_draining.timeout = timeout
+
+	self.conn.modify_lb_attribute(self.balancer.name,
+                                      'ConnectionDraining', attributes.connection_draining)
+
+        attributes = self.balancer.get_attributes()
+        self.assertEqual(enabled, attributes.connection_draining.enabled)
+        if timeout != None:
+            self.assertEqual(timeout, attributes.connection_draining.timeout)
+
+    def test_load_balancer_connection_draining_config(self):
+    	self.change_and_verify_load_balancer_connection_draining(True, 128)
+    	self.change_and_verify_load_balancer_connection_draining(True, 256)
+    	self.change_and_verify_load_balancer_connection_draining(False)
+    	self.change_and_verify_load_balancer_connection_draining(True, 64) 
+
     def test_set_load_balancer_policies_of_listeners(self):
         more_listeners = [(443, 8001, 'HTTP')]
         self.conn.create_load_balancer_listeners(self.name, more_listeners)