← Back to team overview

txaws-dev team mailing list archive

[Merge] lp:~franciscosouza/txaws/txaws-bundled-vpc into lp:txaws

 

Francisco Souza has proposed merging lp:~franciscosouza/txaws/txaws-bundled-vpc into lp:txaws.

Requested reviews:
  txAWS Committers (txaws-dev)

For more details, see:
https://code.launchpad.net/~franciscosouza/txaws/txaws-bundled-vpc/+merge/135397

client, ec2: bundling changes needed for VPC

I use this CL for patching only, original CL's are:

https://codereview.appspot.com/6852064/
https://codereview.appspot.com/6826065/
https://codereview.appspot.com/6814123/
https://codereview.appspot.com/6822097/

This one also includes a change in parsing of instance sets.

https://codereview.appspot.com/6851093/

-- 
https://code.launchpad.net/~franciscosouza/txaws/txaws-bundled-vpc/+merge/135397
Your team txAWS Committers is requested to review the proposed merge of lp:~franciscosouza/txaws/txaws-bundled-vpc into lp:txaws.
=== modified file 'txaws/client/base.py'
--- txaws/client/base.py	2012-05-16 02:35:26 +0000
+++ txaws/client/base.py	2012-11-21 12:35:25 +0000
@@ -1,3 +1,6 @@
+import os
+import urlparse
+
 try:
     from xml.etree.ElementTree import ParseError
 except ImportError:
@@ -6,16 +9,16 @@
 import warnings
 from StringIO import StringIO
 
+from twisted.internet.endpoints import TCP4ClientEndpoint
 from twisted.internet.ssl import ClientContextFactory
 from twisted.internet.protocol import Protocol
 from twisted.internet.defer import Deferred, succeed, fail
 from twisted.python import failure
 from twisted.web import http
 from twisted.web.iweb import UNKNOWN_LENGTH
-from twisted.web.client import HTTPClientFactory
-from twisted.web.client import Agent
+from twisted.web.client import Agent, ProxyAgent
 from twisted.web.client import ResponseDone
-from twisted.web.http import NO_CONTENT
+from twisted.web.http import NO_CONTENT, PotentialDataLoss
 from twisted.web.http_headers import Headers
 from twisted.web.error import Error as TwistedWebError
 try:
@@ -130,7 +133,7 @@
         self._received += len(bytes)
 
     def connectionLost(self, reason):
-        reason.trap(ResponseDone)
+        reason.trap(ResponseDone, PotentialDataLoss)
         d = self.finished
         self.finished = None
         streaming = self.content_length is UNKNOWN_LENGTH
@@ -220,16 +223,28 @@
         if (self.body_producer is None) and (data is not None):
             self.body_producer = FileBodyProducer(StringIO(data))
         if scheme == "https":
-            if self.endpoint.ssl_hostname_verification:
-                contextFactory = WebVerifyingContextFactory(host)
+            proxy_endpoint = os.environ.get("https_proxy")
+            if proxy_endpoint:
+                proxy_url = urlparse.urlparse(proxy_endpoint)
+                endpoint = TCP4ClientEndpoint(self.reactor, proxy_url.hostname, proxy_url.port)
+                agent = ProxyAgent(endpoint)
             else:
-                contextFactory = WebClientContextFactory()
-            agent = Agent(self.reactor, contextFactory)
+                if self.endpoint.ssl_hostname_verification:
+                    contextFactory = WebVerifyingContextFactory(host)
+                else:
+                    contextFactory = WebClientContextFactory()
+                agent = Agent(self.reactor, contextFactory)
             self.client.url = url
             d = agent.request(method, url, self.request_headers,
                 self.body_producer)
         else:
-            agent = Agent(self.reactor)
+            proxy_endpoint = os.environ.get("http_proxy")
+            if proxy_endpoint:
+                proxy_url = urlparse.urlparse(proxy_endpoint)
+                endpoint = TCP4ClientEndpoint(self.reactor, proxy_url.hostname, proxy_url.port)
+                agent = ProxyAgent(endpoint)
+            else:
+                agent = Agent(self.reactor)
             d = agent.request(method, url, self.request_headers,
                 self.body_producer)
         d.addCallback(self._handle_response)

=== modified file 'txaws/client/discover/tests/test_command.py'
--- txaws/client/discover/tests/test_command.py	2012-01-27 02:10:24 +0000
+++ txaws/client/discover/tests/test_command.py	2012-11-21 12:35:25 +0000
@@ -74,9 +74,9 @@
             url = (
                 "http://endpoint?AWSAccessKeyId=key&";
                 "Action=DescribeRegions&"
-                "Signature=3%2BHSkQQosF1Sr9AL3kdY31tEfTWQ2whjJOUSc3kvc2c%3D&"
+                "Signature=7fyxNidMkL%2B85udGOxqm%2BgM2o1gLyeLG2a0UOmfBOXQ%3D&"
                 "SignatureMethod=HmacSHA256&SignatureVersion=2&"
-                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2009-11-30")
+                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2012-08-15")
             self.assertEqual("GET", self.method)
             self.assertEqual(url, self.url)
             self.assertEqual("URL: %s\n"
@@ -99,9 +99,9 @@
             url = (
                 "http://endpoint?AWSAccessKeyId=key&";
                 "Action=DescribeRegions&RegionName.0=us-west-1&"
-                "Signature=6D8aCgSPQOYixowRHy26aRFzK2Vwgixl9uwegYX9nLA%3D&"
+                "Signature=FL4JjDKbWdg531q1KKUPild%2BvyqspA5wxSmOeWXWsJI%3D&"
                 "SignatureMethod=HmacSHA256&SignatureVersion=2&"
-                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2009-11-30")
+                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2012-08-15")
             self.assertEqual("GET", self.method)
             self.assertEqual(url, self.url)
             self.assertEqual("URL: %s\n"
@@ -128,9 +128,9 @@
             url = (
                 "http://endpoint?AWSAccessKeyId=key&";
                 "Action=DescribeRegions&RegionName.0=us-west-1&"
-                "Signature=6D8aCgSPQOYixowRHy26aRFzK2Vwgixl9uwegYX9nLA%3D&"
+                "Signature=FL4JjDKbWdg531q1KKUPild%2BvyqspA5wxSmOeWXWsJI%3D&"
                 "SignatureMethod=HmacSHA256&SignatureVersion=2&"
-                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2009-11-30")
+                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2012-08-15")
             self.assertEqual("GET", self.method)
             self.assertEqual(url, self.url)
             self.assertEqual("URL: %s\n"
@@ -185,9 +185,9 @@
             url = (
                 "http://endpoint?AWSAccessKeyId=key&";
                 "Action=DescribeRegions&RegionName.0=us-west-1&"
-                "Signature=6D8aCgSPQOYixowRHy26aRFzK2Vwgixl9uwegYX9nLA%3D&"
+                "Signature=FL4JjDKbWdg531q1KKUPild%2BvyqspA5wxSmOeWXWsJI%3D&"
                 "SignatureMethod=HmacSHA256&SignatureVersion=2&"
-                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2009-11-30")
+                "Timestamp=2010-06-04T23%3A40%3A00Z&Version=2012-08-15")
             self.assertEqual("GET", self.method)
             self.assertEqual(url, self.url)
             self.assertEqual("URL: %s\n"

=== modified file 'txaws/ec2/client.py'
--- txaws/ec2/client.py	2012-05-05 00:17:02 +0000
+++ txaws/ec2/client.py	2012-11-21 12:35:25 +0000
@@ -48,7 +48,7 @@
     def run_instances(self, image_id, min_count, max_count,
         security_groups=None, key_name=None, instance_type=None,
         user_data=None, availability_zone=None, kernel_id=None,
-        ramdisk_id=None):
+        ramdisk_id=None, subnet_id=None, security_group_ids=None):
         """Run new instances.
 
         TODO: blockDeviceMapping, monitoring, subnetId
@@ -57,9 +57,21 @@
                   "MaxCount": str(max_count)}
         if key_name is not None:
             params["KeyName"] = key_name
-        if security_groups is not None:
+        if subnet_id is not None:
+            params["SubnetId"] = subnet_id
+            if security_group_ids is not None:
+                for i, id in enumerate(security_group_ids):
+                    params["SecurityGroupId.%d" % (i + 1)] = id
+            else:
+                msg = "You must specify the security_group_ids with the subnet_id"
+                raise ValueError(msg)
+        elif security_groups is not None:
             for i, name in enumerate(security_groups):
                 params["SecurityGroup.%d" % (i + 1)] = name
+        else:
+            msg = ("You must specify either the subnet_id and "
+                   "security_group_ids or security_groups")
+            raise ValueError(msg)
         if user_data is not None:
             params["UserData"] = b64encode(user_data)
         if instance_type is not None:
@@ -110,28 +122,37 @@
         d = query.submit()
         return d.addCallback(self.parser.describe_security_groups)
 
-    def create_security_group(self, name, description):
+    def create_security_group(self, name, description, vpc_id=None):
         """Create security group.
 
         @param name: Name of the new security group.
         @param description: Description of the new security group.
+        @param vpc_id: ID of the VPC to which the security group will belong.
         @return: A C{Deferred} that will fire with a truth value for the
             success of the operation.
         """
         parameters = {"GroupName":  name, "GroupDescription": description}
+        if vpc_id:
+            parameters["VpcId"] = vpc_id
         query = self.query_factory(
             action="CreateSecurityGroup", creds=self.creds,
             endpoint=self.endpoint, other_params=parameters)
         d = query.submit()
-        return d.addCallback(self.parser.truth_return)
+        return d.addCallback(self.parser.create_security_group)
 
-    def delete_security_group(self, name):
+    def delete_security_group(self, name=None, id=None):
         """
-        @param name: Name of the new security group.
+        @param name: Name of the security group.
+        @param id: Id of the security group.
         @return: A C{Deferred} that will fire with a truth value for the
             success of the operation.
         """
-        parameter = {"GroupName":  name}
+        if name:
+            parameter = {"GroupName":  name}
+        elif id:
+            parameter = {"GroupId": id}
+        else:
+            raise ValueError("You must provide either the security group name or id")
         query = self.query_factory(
             action="DeleteSecurityGroup", creds=self.creds,
             endpoint=self.endpoint, other_params=parameter)
@@ -139,7 +160,7 @@
         return d.addCallback(self.parser.truth_return)
 
     def authorize_security_group(
-        self, group_name, source_group_name="", source_group_owner_id="",
+        self, group_name=None, group_id=None, source_group_name="", source_group_owner_id="",
         ip_protocol="", from_port="", to_port="", cidr_ip=""):
         """
         There are two ways to use C{authorize_security_group}:
@@ -150,6 +171,8 @@
 
         @param group_name: The group you will be modifying with a new
             authorization.
+        @param group_id: The id of the group you will be modifying with
+            a new authorization.
 
         Optionally, the following parameters:
         @param source_group_name: Name of security group to authorize access to
@@ -188,7 +211,12 @@
             msg = ("You must specify either both group parameters or "
                    "all the ip parameters.")
             raise ValueError(msg)
-        parameters["GroupName"] = group_name
+        if group_id:
+            parameters["GroupId"] = group_id
+        elif group_name:
+            parameters["GroupName"] = group_name
+        else:
+            raise ValueError("You must specify either the group name of the group id.")
         query = self.query_factory(
             action="AuthorizeSecurityGroupIngress", creds=self.creds,
             endpoint=self.endpoint, other_params=parameters)
@@ -224,7 +252,7 @@
         return d
 
     def revoke_security_group(
-        self, group_name, source_group_name="", source_group_owner_id="",
+        self, group_name=None, group_id=None, source_group_name="", source_group_owner_id="",
         ip_protocol="", from_port="", to_port="", cidr_ip=""):
         """
         There are two ways to use C{revoke_security_group}:
@@ -273,7 +301,12 @@
             msg = ("You must specify either both group parameters or "
                    "all the ip parameters.")
             raise ValueError(msg)
-        parameters["GroupName"] = group_name
+        if group_id:
+            parameters["GroupId"] = group_id
+        elif group_name:
+            parameters["GroupName"] = group_name
+        else:
+            raise ValueError("You must specify either the group name of the group id.")
         query = self.query_factory(
             action="RevokeSecurityGroupIngress", creds=self.creds,
             endpoint=self.endpoint, other_params=parameters)
@@ -547,6 +580,10 @@
               ipAddress, stateReason, architecture, rootDeviceName,
               blockDeviceMapping, instanceLifecycle, spotInstanceRequestId.
         """
+        for group_data in instance_data.find("groupSet"):
+            group_id = group_data.findtext("groupId")
+            group_name = group_data.findtext("groupName")
+            reservation.groups.append((group_id, group_name))
         instance_id = instance_data.findtext("instanceId")
         instance_state = instance_data.find(
             "instanceState").findtext("name")
@@ -599,16 +636,10 @@
         results = []
         # May be a more elegant way to do this:
         for reservation_data in root.find("reservationSet"):
-            # Get the security group information.
-            groups = []
-            for group_data in reservation_data.find("groupSet"):
-                group_id = group_data.findtext("groupId")
-                groups.append(group_id)
             # Create a reservation object with the parsed data.
             reservation = model.Reservation(
                 reservation_id=reservation_data.findtext("reservationId"),
-                owner_id=reservation_data.findtext("ownerId"),
-                groups=groups)
+                owner_id=reservation_data.findtext("ownerId"))
             # Get the list of instances.
             instances = self.instances_set(
                 reservation_data, reservation)
@@ -670,6 +701,7 @@
         root = XML(xml_bytes)
         result = []
         for group_info in root.findall("securityGroupInfo/item"):
+            id = group_info.findtext("groupId")
             name = group_info.findtext("groupName")
             description = group_info.findtext("groupDescription")
             owner_id = group_info.findtext("ownerId")
@@ -709,11 +741,15 @@
                               for user_id, group_name in allowed_groups]
 
             security_group = model.SecurityGroup(
-                name, description, owner_id=owner_id,
+                id, name, description, owner_id=owner_id,
                 groups=allowed_groups, ips=allowed_ips)
             result.append(security_group)
         return result
 
+    def create_security_group(self, xml_bytes):
+        root = XML(xml_bytes)
+        return root.findtext("groupId")
+
     def truth_return(self, xml_bytes):
         """Parse the XML for a truth value.
 

=== modified file 'txaws/ec2/model.py'
--- txaws/ec2/model.py	2012-03-02 22:00:10 +0000
+++ txaws/ec2/model.py	2012-11-21 12:35:25 +0000
@@ -80,7 +80,8 @@
     @ivar allowed_ips: The sequence of L{IPPermission} instances for this
         security group.
     """
-    def __init__(self, name, description, owner_id="", groups=None, ips=None):
+    def __init__(self, id, name, description, owner_id="", groups=None, ips=None):
+        self.id = id
         self.name = name
         self.description = description
         self.owner_id = owner_id

=== modified file 'txaws/ec2/tests/test_client.py'
--- txaws/ec2/tests/test_client.py	2012-03-02 22:00:10 +0000
+++ txaws/ec2/tests/test_client.py	2012-11-21 12:35:25 +0000
@@ -171,7 +171,8 @@
         self.assertEquals(reservation.owner_id, "123456789012")
         # check groups
         group = reservation.groups[0]
-        self.assertEquals(group, "default")
+        self.assertEquals(group[0], "sg-64f9eb08")
+        self.assertEquals(group[1], "default")
         # check instance
         self.assertEquals(instance.instance_id, "i-abcdef01")
         self.assertEquals(instance.instance_state, "running")
@@ -201,7 +202,8 @@
         self.assertEquals(reservation.owner_id, "123456789012")
         # check groups
         group = reservation.groups[0]
-        self.assertEquals(group, "default")
+        self.assertEquals(group[0], "sg-64f9eb08")
+        self.assertEquals(group[1], "default")
         # check instance
         self.assertEquals(instance.instance_id, "i-abcdef01")
         self.assertEquals(instance.instance_state, "running")
@@ -331,7 +333,8 @@
         self.assertEquals(reservation.owner_id, "495219933132")
         # check groups
         group = reservation.groups[0]
-        self.assertEquals(group, "default")
+        self.assertEquals(group[0], "sg-64f9eb08")
+        self.assertEquals(group[1], "default")
         # check instance
         self.assertEquals(instance.instance_id, "i-2ba64342")
         self.assertEquals(instance.instance_state, "pending")
@@ -377,6 +380,59 @@
             ramdisk_id=u"r-1234")
         d.addCallback(self.check_parsed_run_instances)
 
+    def test_run_instances_with_subnet(self):
+        class StubQuery(object):
+            def __init__(stub, action="", creds=None, endpoint=None,
+                         other_params={}):
+                self.assertEqual(action, "RunInstances")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEquals(
+                    other_params,
+                    {"ImageId": "ami-1234", "MaxCount": "2", "MinCount": "1",
+                     "SecurityGroupId.1": u"sg-a72d9f92e", "KeyName": u"default",
+                     "UserData": "Zm9v", "InstanceType": u"m1.small",
+                     "Placement.AvailabilityZone": u"us-east-1b",
+                     "KernelId": u"k-1234", "RamdiskId": u"r-1234",
+                     "SubnetId": "subnet-a72d829f"})
+
+            def submit(self):
+                return succeed(
+                    payload.sample_run_instances_result)
+
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds, query_factory=StubQuery)
+        d = ec2.run_instances("ami-1234", 1, 2, security_group_ids=[u"sg-a72d9f92e"],
+            key_name=u"default", user_data=u"foo", instance_type=u"m1.small",
+            availability_zone=u"us-east-1b", kernel_id=u"k-1234",
+            ramdisk_id=u"r-1234", subnet_id="subnet-a72d829f")
+        d.addCallback(self.check_parsed_run_instances)
+
+    def test_run_instances_with_subnet_but_without_secgroup_id(self):
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds)
+        error = self.assertRaises(ValueError, ec2.run_instances, "ami-1234", 1, 2,
+            key_name=u"default", user_data=u"foo", instance_type=u"m1.small",
+            availability_zone=u"us-east-1b", kernel_id=u"k-1234",
+            ramdisk_id=u"r-1234", subnet_id="subnet-a72d829f")
+        self.assertEqual(
+            str(error),
+            "You must specify the security_group_ids with the subnet_id"
+        )
+
+    def test_run_instances_without_subnet_and_secgroups(self):
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds)
+        error = self.assertRaises(ValueError, ec2.run_instances, "ami-1234", 1, 2,
+            key_name=u"default", user_data=u"foo", instance_type=u"m1.small",
+            availability_zone=u"us-east-1b", kernel_id=u"k-1234",
+            ramdisk_id=u"r-1234")
+        self.assertEqual(
+            str(error),
+            ("You must specify either the subnet_id and "
+             "security_group_ids or security_groups")
+        )
+
 
 class EC2ClientSecurityGroupsTestCase(TXAWSTestCase):
 
@@ -400,6 +456,7 @@
 
         def check_results(security_groups):
             [security_group] = security_groups
+            self.assertEquals(security_group.id, "sg-a1a1a1")
             self.assertEquals(security_group.owner_id,
                               "UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM")
             self.assertEquals(security_group.name, "WebServers")
@@ -440,6 +497,7 @@
             security_group = security_groups[0]
             self.assertEquals(security_group.owner_id,
                               "UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM")
+            self.assertEquals(security_group.id, "sg-a1a1a1")
             self.assertEquals(security_group.name, "MessageServers")
             self.assertEquals(security_group.description, "Message Servers")
             self.assertEquals(security_group.allowed_groups, [])
@@ -451,6 +509,7 @@
             security_group = security_groups[1]
             self.assertEquals(security_group.owner_id,
                               "UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM")
+            self.assertEquals(security_group.id, "sg-c3c3c3")
             self.assertEquals(security_group.name, "WebServers")
             self.assertEquals(security_group.description, "Web Servers")
             self.assertEquals([(pair.user_id, pair.group_name)
@@ -583,14 +642,45 @@
             def submit(self):
                 return succeed(payload.sample_create_security_group)
 
+        def check_result(id):
+            self.assertEquals(id, "sg-1a2b3c4d")
+
         creds = AWSCredentials("foo", "bar")
         ec2 = client.EC2Client(creds, query_factory=StubQuery)
         d = ec2.create_security_group(
             "WebServers",
             "The group for the web server farm.")
-        return self.assertTrue(d)
-
-    def test_delete_security_group(self):
+        return d.addCallback(check_result)
+
+    def test_create_security_group_with_VPC(self):
+        class StubQuery(object):
+
+            def __init__(stub, action="", creds=None, endpoint=None,
+                         other_params={}):
+                self.assertEqual(action, "CreateSecurityGroup")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(other_params, {
+                    "GroupName": "WebServers",
+                    "GroupDescription": "The group for the web server farm.",
+                    "VpcId": "vpc-a4f2",
+                    })
+
+            def submit(self):
+                return succeed(payload.sample_create_security_group)
+
+        def check_result(id):
+            self.assertEquals(id, "sg-1a2b3c4d")
+
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds, query_factory=StubQuery)
+        d = ec2.create_security_group(
+            "WebServers",
+            "The group for the web server farm.",
+            "vpc-a4f2")
+        return d.addCallback(check_result)
+
+    def test_delete_security_group_using_name(self):
         """
         L{EC2Client.delete_security_group} returns a C{Deferred} that
         eventually fires with a true value, indicating the success of the
@@ -615,6 +705,40 @@
         d = ec2.delete_security_group("WebServers")
         return self.assertTrue(d)
 
+    def test_delete_security_group_using_id(self):
+        """
+        L{EC2Client.delete_security_group} returns a C{Deferred} that
+        eventually fires with a true value, indicating the success of the
+        operation.
+        """
+        class StubQuery(object):
+
+            def __init__(stub, action="", creds=None, endpoint=None,
+                         other_params={}):
+                self.assertEqual(action, "DeleteSecurityGroup")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(other_params, {
+                    "GroupId": "sg-a1a1a1",
+                    })
+
+            def submit(self):
+                return succeed(payload.sample_delete_security_group)
+
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds, query_factory=StubQuery)
+        d = ec2.delete_security_group(id="sg-a1a1a1")
+        return self.assertTrue(d)
+
+    def test_delete_security_group_without_id_and_name(self):
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds)
+        error = self.assertRaises(ValueError, ec2.delete_security_group)
+        self.assertEquals(
+            str(error),
+            "You must provide either the security group name or id",
+        )
+
     def test_delete_security_group_failure(self):
         """
         L{EC2Client.delete_security_group} returns a C{Deferred} that
@@ -676,9 +800,42 @@
         creds = AWSCredentials("foo", "bar")
         ec2 = client.EC2Client(creds, query_factory=StubQuery)
         d = ec2.authorize_security_group(
-            "WebServers", source_group_name="AppServers",
-            source_group_owner_id="123456789123")
-        return self.assertTrue(d)
+            group_name="WebServers", source_group_name="AppServers",
+            source_group_owner_id="123456789123")
+        return self.assertTrue(d)
+
+    def test_authorize_security_group_using_group_id(self):
+        class StubQuery(object):
+
+            def __init__(stub, action="", creds=None, endpoint=None,
+                         other_params={}):
+                self.assertEqual(action, "AuthorizeSecurityGroupIngress")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(other_params, {
+                    "GroupId": "sg-a1b2c3d4e5f6",
+                    "SourceSecurityGroupName": "AppServers",
+                    "SourceSecurityGroupOwnerId": "123456789123",
+                    })
+
+            def submit(self):
+                return succeed(payload.sample_authorize_security_group)
+
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds, query_factory=StubQuery)
+        d = ec2.authorize_security_group(
+            group_id="sg-a1b2c3d4e5f6", source_group_name="AppServers",
+            source_group_owner_id="123456789123")
+        return self.assertTrue(d)
+
+    def test_authorize_security_group_without_group_id_and_group_name(self):
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds)
+        error = self.assertRaises(ValueError, ec2.authorize_security_group,
+                source_group_name="AppServers", source_group_owner_id="123456789123")
+        self.assertEquals(
+            str(error),
+            "You must specify either the group name of the group id.")
 
     def test_authorize_security_group_with_ip_permissions(self):
         """
@@ -707,7 +864,7 @@
         creds = AWSCredentials("foo", "bar")
         ec2 = client.EC2Client(creds, query_factory=StubQuery)
         d = ec2.authorize_security_group(
-            "WebServers", ip_protocol="tcp", from_port="22", to_port="80",
+            group_name="WebServers", ip_protocol="tcp", from_port="22", to_port="80",
             cidr_ip="0.0.0.0/0")
         return self.assertTrue(d)
 
@@ -722,16 +879,12 @@
         """
         creds = AWSCredentials("foo", "bar")
         ec2 = client.EC2Client(creds)
-        self.assertRaises(ValueError, ec2.authorize_security_group,
-                "WebServers", ip_protocol="tcp", from_port="22")
-        try:
-            ec2.authorize_security_group(
-                "WebServers", ip_protocol="tcp", from_port="22")
-        except Exception, error:
-            self.assertEquals(
-                str(error),
-                ("You must specify either both group parameters or all the "
-                 "ip parameters."))
+        error = self.assertRaises(ValueError, ec2.authorize_security_group,
+                group_name="WebServers", ip_protocol="tcp", from_port="22")
+        self.assertEquals(
+            str(error),
+            ("You must specify either both group parameters or all the "
+             "ip parameters."))
 
     def test_authorize_group_permission(self):
         """
@@ -822,6 +975,30 @@
             source_group_owner_id="123456789123")
         return self.assertTrue(d)
 
+    def test_revoke_security_group_using_group_id(self):
+        class StubQuery(object):
+
+            def __init__(stub, action="", creds=None, endpoint=None,
+                         other_params={}):
+                self.assertEqual(action, "RevokeSecurityGroupIngress")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(other_params, {
+                    "GroupId": "sg-a1a1a1",
+                    "SourceSecurityGroupName": "AppServers",
+                    "SourceSecurityGroupOwnerId": "123456789123",
+                    })
+
+            def submit(self):
+                return succeed(payload.sample_revoke_security_group)
+
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds, query_factory=StubQuery)
+        d = ec2.revoke_security_group(
+            group_id="sg-a1a1a1", source_group_name="AppServers",
+            source_group_owner_id="123456789123")
+        return self.assertTrue(d)
+
     def test_revoke_security_group_with_ip_permissions(self):
         """
         L{EC2Client.revoke_security_group} returns a C{Deferred} that
@@ -853,6 +1030,15 @@
             cidr_ip="0.0.0.0/0")
         return self.assertTrue(d)
 
+    def test_revoke_security_group_without_group_id_and_group_name(self):
+        creds = AWSCredentials("foo", "bar")
+        ec2 = client.EC2Client(creds)
+        error = self.assertRaises(ValueError, ec2.revoke_security_group,
+                source_group_name="AppServers", source_group_owner_id="123456789123")
+        self.assertEquals(
+            str(error),
+            "You must specify either the group name of the group id.")
+
     def test_revoke_security_group_with_missing_parameters(self):
         """
         L{EC2Client.revoke_security_group} returns a C{Deferred} that
@@ -864,16 +1050,12 @@
         """
         creds = AWSCredentials("foo", "bar")
         ec2 = client.EC2Client(creds)
-        self.assertRaises(ValueError, ec2.authorize_security_group,
-                "WebServers", ip_protocol="tcp", from_port="22")
-        try:
-            ec2.authorize_security_group(
-                "WebServers", ip_protocol="tcp", from_port="22")
-        except Exception, error:
-            self.assertEquals(
-                str(error),
-                ("You must specify either both group parameters or all the "
-                 "ip parameters."))
+        error = self.assertRaises(ValueError, ec2.revoke_security_group,
+                group_name="WebServers", ip_protocol="tcp", from_port="22")
+        self.assertEquals(
+            str(error),
+            ("You must specify either both group parameters or all the "
+             "ip parameters."))
 
     def test_revoke_group_permission(self):
         """
@@ -1561,7 +1743,7 @@
             {"AWSAccessKeyId": "foo",
              "Action": "DescribeInstances",
              "SignatureVersion": "2",
-             "Version": "2009-11-30"})
+             "Version": "2012-08-15"})
 
     def test_init_other_args_are_params(self):
         query = client.Query(
@@ -1575,7 +1757,7 @@
              "InstanceId.0": "12345",
              "SignatureVersion": "2",
              "Timestamp": "2007-11-12T13:14:15Z",
-             "Version": "2009-11-30"})
+             "Version": "2012-08-15"})
 
     def test_no_timestamp_if_expires_in_other_params(self):
         """
@@ -1593,7 +1775,7 @@
              "Action": "DescribeInstances",
              "SignatureVersion": "2",
              "Expires": "2007-11-12T13:14:15Z",
-             "Version": "2009-11-30"})
+             "Version": "2012-08-15"})
 
     def test_sign(self):
         query = client.Query(
@@ -1601,7 +1783,7 @@
             endpoint=self.endpoint,
             time_tuple=(2007, 11, 12, 13, 14, 15, 0, 0, 0))
         query.sign()
-        self.assertEqual("G4c2NtQaFNhWWT8EWPVIIOpHVr0mGUYwJVYss9krsMU=",
+        self.assertEqual("c0gbkemrGEJdqxWOl2UZYaygYiBLVjrpWBs7bTN7Ndo=",
             query.params["Signature"])
 
     def test_old_sign(self):
@@ -1612,7 +1794,7 @@
             other_params={"SignatureVersion": "1"})
         query.sign()
         self.assertEqual(
-            "9xP+PIs/3QXW+4mWX6WGR4nGqfE=", query.params["Signature"])
+            "7tWrIC5VYvXOjVE+roVoyDUt2Yw=", query.params["Signature"])
 
     def test_unsupported_sign(self):
         query = client.Query(

=== modified file 'txaws/ec2/tests/test_model.py'
--- txaws/ec2/tests/test_model.py	2012-01-23 01:04:25 +0000
+++ txaws/ec2/tests/test_model.py	2012-11-21 12:35:25 +0000
@@ -8,7 +8,8 @@
 class SecurityGroupTestCase(TXAWSTestCase):
 
     def test_creation_defaults(self):
-        group = model.SecurityGroup("name", "desc")
+        group = model.SecurityGroup("sg-a3f2", "name", "desc")
+        self.assertEquals(group.id, "sg-a3f2")
         self.assertEquals(group.name, "name")
         self.assertEquals(group.description, "desc")
         self.assertEquals(group.owner_id, "")
@@ -18,14 +19,15 @@
     def test_creation_all_parameters(self):
         user = "somegal24"
         other_groups = [
-            model.SecurityGroup("other1", "another group 1"),
-            model.SecurityGroup("other2", "another group 2")]
+            model.SecurityGroup("sg-other1", "other1", "another group 1"),
+            model.SecurityGroup("sg-other2", "other2", "another group 2")]
         user_group_pairs = [
             model.UserIDGroupPair(user, other_groups[0].name),
             model.UserIDGroupPair(user, other_groups[1].name)]
         ips = [model.IPPermission("tcp", "80", "80", "10.0.1.0/24")]
         group = model.SecurityGroup(
-            "name", "desc", owner_id="me", groups=user_group_pairs, ips=ips)
+            "id", "name", "desc", owner_id="me", groups=user_group_pairs, ips=ips)
+        self.assertEquals(group.id, "id")
         self.assertEquals(group.name, "name")
         self.assertEquals(group.description, "desc")
         self.assertEquals(group.owner_id, "me")

=== modified file 'txaws/server/tests/test_call.py'
--- txaws/server/tests/test_call.py	2012-01-27 02:10:24 +0000
+++ txaws/server/tests/test_call.py	2012-11-21 12:35:25 +0000
@@ -11,4 +11,4 @@
         2009-11-30, which is the earliest version we support.
         """
         call = Call()
-        self.assertEqual(call.version, "2009-11-30")
+        self.assertEqual(call.version, "2012-08-15")

=== modified file 'txaws/testing/payload.py'
--- txaws/testing/payload.py	2012-05-16 02:47:12 +0000
+++ txaws/testing/payload.py	2012-11-21 12:35:25 +0000
@@ -9,11 +9,7 @@
     <item>
       <reservationId>r-cf24b1a6</reservationId>
       <ownerId>123456789012</ownerId>
-      <groupSet>
-        <item>
-          <groupId>default</groupId>
-        </item>
-      </groupSet>
+      <groupSet/>
       <instancesSet>
         <item>
           <instanceId>i-abcdef01</instanceId>
@@ -22,6 +18,12 @@
             <code>16</code>
             <name>running</name>
           </instanceState>
+          <groupSet>
+            <item>
+              <groupId>sg-64f9eb08</groupId>
+              <groupName>default</groupName>
+            </item>
+          </groupSet>
           <privateDnsName>domU-12-31-39-03-15-11.compute-1.internal\
 </privateDnsName>
           <dnsName>ec2-75-101-245-65.compute-1.amazonaws.com</dnsName>
@@ -48,11 +50,7 @@
     <item>
       <reservationId>r-cf24b1a6</reservationId>
       <ownerId>123456789012</ownerId>
-      <groupSet>
-        <item>
-          <groupId>default</groupId>
-        </item>
-      </groupSet>
+      <groupSet/>
       <instancesSet>
         <item>
           <instanceId>i-abcdef01</instanceId>
@@ -67,6 +65,12 @@
           <privateIpAddress>10.0.0.1</privateIpAddress>
           <ipAddress>75.101.245.65</ipAddress>
           <reason/>
+          <groupSet>
+            <item>
+              <groupId>sg-64f9eb08</groupId>
+              <groupName>default</groupName>
+            </item>
+          </groupSet>
           <keyName>keyname</keyName>
           <amiLaunchIndex>0</amiLaunchIndex>
           <productCodes>
@@ -92,18 +96,14 @@
 <RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/%s/";>
   <reservationId>r-47a5402e</reservationId>
   <ownerId>495219933132</ownerId>
-  <groupSet>
-    <item>
-      <groupId>default</groupId>
-    </item>
-  </groupSet>
+  <groupSet/>
   <instancesSet>
     <item>
       <instanceId>i-2ba64342</instanceId>
       <imageId>ami-60a54009</imageId>
       <instanceState>
         <code>0</code>
-    <name>pending</name>
+        <name>pending</name>
       </instanceState>
       <privateDnsName></privateDnsName>
       <dnsName></dnsName>
@@ -111,6 +111,12 @@
        <amiLaunchIndex>0</amiLaunchIndex>
       <instanceType>m1.small</instanceType>
       <launchTime>2007-08-07T11:51:50.000Z</launchTime>
+      <groupSet>
+        <item>
+          <groupId>sg-64f9eb08</groupId>
+          <groupName>default</groupName>
+        </item>
+      </groupSet>
       <placement>
         <availabilityZone>us-east-1b</availabilityZone>
       </placement>
@@ -120,7 +126,7 @@
       <imageId>ami-60a54009</imageId>
       <instanceState>
         <code>0</code>
-    <name>pending</name>
+        <name>pending</name>
       </instanceState>
       <privateDnsName></privateDnsName>
       <dnsName></dnsName>
@@ -128,6 +134,12 @@
       <amiLaunchIndex>1</amiLaunchIndex>
       <instanceType>m1.small</instanceType>
       <launchTime>2007-08-07T11:51:50.000Z</launchTime>
+      <groupSet>
+        <item>
+          <groupId>sg-64f9eb08</groupId>
+          <groupName>default</groupName>
+        </item>
+      </groupSet>
       <placement>
         <availabilityZone>us-east-1b</availabilityZone>
       </placement>
@@ -145,6 +157,12 @@
       <amiLaunchIndex>2</amiLaunchIndex>
       <instanceType>m1.small</instanceType>
       <launchTime>2007-08-07T11:51:50.000Z</launchTime>
+      <groupSet>
+        <item>
+          <groupId>sg-64f9eb08</groupId>
+          <groupName>default</groupName>
+        </item>
+      </groupSet>
       <placement>
         <availabilityZone>us-east-1b</availabilityZone>
       </placement>
@@ -213,6 +231,7 @@
          <fromPort/>
         </item>
       </ipPermissions>
+      <groupId>sg-a1a1a1</groupId>
       <groupName>WebServers</groupName>
       <groupDescription>Web servers</groupDescription>
       <ownerId>UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM</ownerId>
@@ -228,6 +247,7 @@
   <securityGroupInfo>
     <item>
       <ownerId>UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM</ownerId>
+      <groupId>sg-a1a1a1</groupId>
       <groupName>WebServers</groupName>
       <groupDescription>Web Servers</groupDescription>
       <ipPermissions>
@@ -256,6 +276,7 @@
   <securityGroupInfo>
     <item>
       <ownerId>UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM</ownerId>
+      <groupId>sg-a1a1a1</groupId>
       <groupName>MessageServers</groupName>
       <groupDescription>Message Servers</groupDescription>
       <ipPermissions>
@@ -274,6 +295,7 @@
     </item>
     <item>
       <ownerId>UYY3TLBUXIEON5NQVUUX6OMPWBZIQNFM</ownerId>
+      <groupId>sg-c3c3c3</groupId>
       <groupName>WebServers</groupName>
       <groupDescription>Web Servers</groupDescription>
       <ipPermissions>
@@ -457,6 +479,7 @@
 sample_create_security_group = """\
 <CreateSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/%s/";>
   <return>true</return>
+  <groupId>sg-1a2b3c4d</groupId>
 </CreateSecurityGroupResponse>
 """ % (version.ec2_api,)
 

=== modified file 'txaws/version.py'
--- txaws/version.py	2012-01-24 23:18:36 +0000
+++ txaws/version.py	2012-11-21 12:35:25 +0000
@@ -1,3 +1,3 @@
 txaws = "0.2.3"
-ec2_api = "2009-11-30"
+ec2_api = "2012-08-15"
 s3_api = "2006-03-01"


Follow ups