Merge lp:~dimitern/goamz/subnets-api-calls into lp:goamz

Proposed by Dimiter Naydenov
Status: Merged
Merged at revision: 47
Proposed branch: lp:~dimitern/goamz/subnets-api-calls
Merge into: lp:goamz
Prerequisite: lp:~dimitern/goamz/vpc-api-calls
Diff against target: 593 lines (+513/-0)
5 files modified
ec2/ec2test/server.go (+105/-0)
ec2/responses_test.go (+55/-0)
ec2/subnets.go (+111/-0)
ec2/subnets_test.go (+212/-0)
ec2/vpc_test.go (+30/-0)
To merge this branch: bzr merge lp:~dimitern/goamz/subnets-api-calls
Reviewer Review Type Date Requested Status
Dimiter Naydenov (community) Approve
Review via email: mp+204640@code.launchpad.net

Description of the change

ec2: Add support for VPC subnets

Added the following new API calls:
- CreateSubnet
- DeleteSubnet
- Subnets
(and related types/responses)

This is the second step on the path to
support VPC networking in goamz, next
APIs for network interfaces will be
added.

Tested live on EC2, and extended the
ec2test package as needed.

Added a deleteVPCs test helpers which
waits until a VPC is no longer in use
and can be deleted, retrying as needed
when running against live EC2 servers.
This is needed to ensure live tests do
no leave stuff behind (I've run all live
tests several times in a row to make sure
it works).

https://codereview.appspot.com/54690048/

To post a comment you must log in.
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Reviewers: mp+204640_code.launchpad.net,

Message:
Please take a look.

Description:
ec2: Add support for VPC subnets

Added the following new API calls:
- CreateSubnet
- DeleteSubnet
- DescribeSubnets
(and related types/responses)

This is the second step on the path to
support VPC networking in goamz, next
APIs for network interfaces will be
added.

Using AWS API version 2011-12-15.

Tested live on EC2, and extended the
ec2test package as needed.

https://code.launchpad.net/~dimitern/goamz/subnets-api-calls/+merge/204640

Requires:
https://code.launchpad.net/~dimitern/goamz/vpc-api-calls/+merge/204514

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/54690048/

Affected files (+466, -2 lines):
   A [revision details]
   M ec2/ec2test/server.go
   M ec2/responses_test.go
   A ec2/subnets.go
   A ec2/subnets_test.go

lp:~dimitern/goamz/subnets-api-calls updated
51. By Dimiter Naydenov

Minor fixes

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
lp:~dimitern/goamz/subnets-api-calls updated
52. By Dimiter Naydenov

Merged conflicts; simplified & refactored a bit; naming fixed, API version updated

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :

LGTM with some suggestions below.

https://codereview.appspot.com/54690048/diff/40001/ec2/ec2test/server.go
File ec2/ec2test/server.go (right):

https://codereview.appspot.com/54690048/diff/40001/ec2/ec2test/server.go#newcode1105
ec2/ec2test/server.go:1105: availIPs :=
int(math.Exp2(float64(maskBits-maskOnes))) - 5
availIPs := 1<<uint(maskBits - maskOnes) - 5
?

i suppose we might consider making sure that there
are enough bits in the mask too, otherwise
we can get a negative AvailableIPCount.

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go
File ec2/subnets.go (right):

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go#newcode42
ec2/subnets.go:42: // When you create each subnet, you provide the VPC
ID and the CIDR
// The vpcId and cidrBlock parameters specify the
// VPC id and CIDR block respectively - these cannot
// be changed after creation. The subnet's CIDR block can be the same as
// the VPC's CIDR block (assuming you want only a single subnet in the
// VPC), or a subset of the VPC's CIDR block. If more than one
// subnet is created in a VPC, their CIDR blocks must not overlap. The
// smallest subnet (and VPC) that can be created uses a /28 netmask (16
IP
// addresses), and the largest uses a /16 netmask (65,536 IP
// addresses).

?

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go#newcode52
ec2/subnets.go:52: // availZone can be empty, in which case Amazon EC2
selects one for
s/can/may/

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go#newcode71
ec2/subnets.go:71: // DeleteSubnet deletes the specified subnet. You
must terminate all
// All running instances in the subnet must have been terminated.

?

https://codereview.appspot.com/54690048/

lp:~dimitern/goamz/subnets-api-calls updated
53. By Dimiter Naydenov

Changes after reivew

54. By Dimiter Naydenov

Merged vpc-api-calls into subnets-api-calls.

55. By Dimiter Naydenov

Improved tests not to leave stuff around live

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please take a look.

https://codereview.appspot.com/54690048/diff/40001/ec2/ec2test/server.go
File ec2/ec2test/server.go (right):

https://codereview.appspot.com/54690048/diff/40001/ec2/ec2test/server.go#newcode1105
ec2/ec2test/server.go:1105: availIPs :=
int(math.Exp2(float64(maskBits-maskOnes))) - 5
On 2014/02/05 14:46:53, rog wrote:
> availIPs := 1<<uint(maskBits - maskOnes) - 5
> ?

> i suppose we might consider making sure that there
> are enough bits in the mask too, otherwise
> we can get a negative AvailableIPCount.

Done.

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go
File ec2/subnets.go (right):

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go#newcode42
ec2/subnets.go:42: // When you create each subnet, you provide the VPC
ID and the CIDR
On 2014/02/05 14:46:53, rog wrote:
> // The vpcId and cidrBlock parameters specify the
> // VPC id and CIDR block respectively - these cannot
> // be changed after creation. The subnet's CIDR block can be the same
as
> // the VPC's CIDR block (assuming you want only a single subnet in the
> // VPC), or a subset of the VPC's CIDR block. If more than one
> // subnet is created in a VPC, their CIDR blocks must not overlap.
The
> // smallest subnet (and VPC) that can be created uses a /28 netmask
(16 IP
> // addresses), and the largest uses a /16 netmask (65,536 IP
> // addresses).

> ?

Done.

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go#newcode52
ec2/subnets.go:52: // availZone can be empty, in which case Amazon EC2
selects one for
On 2014/02/05 14:46:53, rog wrote:
> s/can/may/

Done.

https://codereview.appspot.com/54690048/diff/40001/ec2/subnets.go#newcode71
ec2/subnets.go:71: // DeleteSubnet deletes the specified subnet. You
must terminate all
On 2014/02/05 14:46:53, rog wrote:
> // All running instances in the subnet must have been terminated.

> ?

Done.

https://codereview.appspot.com/54690048/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
Martin Packman (gz) wrote :

LGTM.

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets.go
File ec2/subnets.go (right):

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets.go#newcode8
ec2/subnets.go:8: // Written by Gustavo Niemeyer
<email address hidden>
Er, I'm not sure what Gustavo thinks here, but should bump the copyright
year at least when adding a new file, if now the authorship bit (I'd
just omit that as policy). This probably applies to the other merge
proposals as well.

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets_test.go
File ec2/subnets_test.go (right):

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets_test.go#newcode141
ec2/subnets_test.go:141: for a := testAttempt.Start(); a.Next(); {
Hm, this looks familiar. :)

At third one, should figure out how to make a helper for this.

https://codereview.appspot.com/54690048/diff/80001/ec2/vpc_test.go
File ec2/vpc_test.go (right):

https://codereview.appspot.com/54690048/diff/80001/ec2/vpc_test.go#newcode155
ec2/vpc_test.go:155: func (s *ServerTests) deleteVPCs(c *C, ids
[]string) {
Somewher later in the pipe this gets picked up for the existing vpc live
test too?

https://codereview.appspot.com/54690048/

lp:~dimitern/goamz/subnets-api-calls updated
56. By Dimiter Naydenov

Merged vpc-api-calls into subnets-api-calls.

57. By Dimiter Naydenov

Changes after reivew

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please take a look.

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets.go
File ec2/subnets.go (right):

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets.go#newcode8
ec2/subnets.go:8: // Written by Gustavo Niemeyer
<email address hidden>
On 2014/02/06 16:37:42, gz wrote:
> Er, I'm not sure what Gustavo thinks here, but should bump the
copyright year at
> least when adding a new file, if now the authorship bit (I'd just omit
that as
> policy). This probably applies to the other merge proposals as well.

I'll change the year.

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets_test.go
File ec2/subnets_test.go (right):

https://codereview.appspot.com/54690048/diff/80001/ec2/subnets_test.go#newcode141
ec2/subnets_test.go:141: for a := testAttempt.Start(); a.Next(); {
On 2014/02/06 16:37:42, gz wrote:
> Hm, this looks familiar. :)

> At third one, should figure out how to make a helper for this.

The problem is in all cases the code is similar, but it needs to call a
different "list" call and process a different slice of things. I'm not
sure how to make it unified (without generics, that is).

https://codereview.appspot.com/54690048/diff/80001/ec2/vpc_test.go
File ec2/vpc_test.go (right):

https://codereview.appspot.com/54690048/diff/80001/ec2/vpc_test.go#newcode155
ec2/vpc_test.go:155: func (s *ServerTests) deleteVPCs(c *C, ids
[]string) {
On 2014/02/06 16:37:42, gz wrote:
> Somewher later in the pipe this gets picked up for the existing vpc
live test
> too?

No, because there's no need - TestVPCs does not create any dependent
objects, so VPC can be removed right away, but other cases where you
need a VPC, a subnet and something else, deleteVPCs is useful.

https://codereview.appspot.com/54690048/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

LGTM

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go
File ec2/subnets.go (right):

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode8
ec2/subnets.go:8: // Written by Gustavo Niemeyer
<email address hidden>
Please drop this line.

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode45
ec2/subnets.go:45: // (assuming you want only a single subnet in the
VPC), or a subset of
Documentation should be impersonal:

     "assuming a single subnet is wanted"

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode51
ec2/subnets.go:51: // availZone may be empty, in which case Amazon EC2
selects one for
// If availZone is empty, an availability zone is automatically
// selected.

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode93
ec2/subnets.go:93: // Subnets describes one or more of your subnets.
Both parameters are
// Subnets returns one or more subnets. Both ...

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets_test.go
File ec2/subnets_test.go (right):

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets_test.go#newcode8
ec2/subnets_test.go:8: // Written by Gustavo Niemeyer
<email address hidden>
Please drop this line.

https://codereview.appspot.com/54690048/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

*** Submitted:

ec2: Add support for VPC subnets

Added the following new API calls:
- CreateSubnet
- DeleteSubnet
- Subnets
(and related types/responses)

This is the second step on the path to
support VPC networking in goamz, next
APIs for network interfaces will be
added.

Tested live on EC2, and extended the
ec2test package as needed.

Added a deleteVPCs test helpers which
waits until a VPC is no longer in use
and can be deleted, retrying as needed
when running against live EC2 servers.
This is needed to ensure live tests do
no leave stuff behind (I've run all live
tests several times in a row to make sure
it works).

R=rog, gz, niemeyer
CC=
https://codereview.appspot.com/54690048

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go
File ec2/subnets.go (right):

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode8
ec2/subnets.go:8: // Written by Gustavo Niemeyer
<email address hidden>
On 2014/02/12 13:33:51, niemeyer wrote:
> Please drop this line.

Done.

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode45
ec2/subnets.go:45: // (assuming you want only a single subnet in the
VPC), or a subset of
On 2014/02/12 13:33:51, niemeyer wrote:
> Documentation should be impersonal:

> "assuming a single subnet is wanted"

Done.

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode51
ec2/subnets.go:51: // availZone may be empty, in which case Amazon EC2
selects one for
On 2014/02/12 13:33:51, niemeyer wrote:
> // If availZone is empty, an availability zone is automatically
> // selected.

Done.

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets.go#newcode93
ec2/subnets.go:93: // Subnets describes one or more of your subnets.
Both parameters are
On 2014/02/12 13:33:51, niemeyer wrote:
> // Subnets returns one or more subnets. Both ...

Done.

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets_test.go
File ec2/subnets_test.go (right):

https://codereview.appspot.com/54690048/diff/100001/ec2/subnets_test.go#newcode8
ec2/subnets_test.go:8: // Written by Gustavo Niemeyer
<email address hidden>
On 2014/02/12 13:33:51, niemeyer wrote:
> Please drop this line.

Done.

https://codereview.appspot.com/54690048/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Post-submit approval.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ec2/ec2test/server.go'
2--- ec2/ec2test/server.go 2014-02-07 12:48:07 +0000
3+++ ec2/ec2test/server.go 2014-02-07 12:48:07 +0000
4@@ -51,12 +51,14 @@
5 reservations map[string]*reservation // id -> reservation
6 groups map[string]*securityGroup // id -> group
7 vpcs map[string]*vpc // id -> vpc
8+ subnets map[string]*subnet // id -> subnet
9 maxId counter
10 reqId counter
11 reservationId counter
12 groupId counter
13 vpcId counter
14 dhcpOptsId counter
15+ subnetId counter
16 initialInstanceState ec2.InstanceState
17 }
18
19@@ -212,6 +214,28 @@
20 return false, fmt.Errorf("unknown attribute %q", attr)
21 }
22
23+type subnet struct {
24+ ec2.Subnet
25+}
26+
27+func (s *subnet) matchAttr(attr, value string) (ok bool, err error) {
28+ switch attr {
29+ case "cidr":
30+ return s.CIDRBlock == value, nil
31+ case "availability-zone":
32+ return s.AvailZone == value, nil
33+ case "state":
34+ return s.State == value, nil
35+ case "subnet-id":
36+ return s.Id == value, nil
37+ case "vpc-id":
38+ return s.VPCId == value, nil
39+ case "tag", "tag-key", "tag-value", "available-ip-address-count", "defaultForAz":
40+ return false, fmt.Errorf("%q filter not implemented", attr)
41+ }
42+ return false, fmt.Errorf("unknown attribute %q", attr)
43+}
44+
45 var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) interface{}{
46 "RunInstances": (*Server).runInstances,
47 "TerminateInstances": (*Server).terminateInstances,
48@@ -224,6 +248,9 @@
49 "CreateVpc": (*Server).createVpc,
50 "DeleteVpc": (*Server).deleteVpc,
51 "DescribeVpcs": (*Server).describeVpcs,
52+ "CreateSubnet": (*Server).createSubnet,
53+ "DeleteSubnet": (*Server).deleteSubnet,
54+ "DescribeSubnets": (*Server).describeSubnets,
55 }
56
57 const ownerId = "9876"
58@@ -245,6 +272,7 @@
59 instances: make(map[string]*Instance),
60 groups: make(map[string]*securityGroup),
61 vpcs: make(map[string]*vpc),
62+ subnets: make(map[string]*subnet),
63 reservations: make(map[string]*reservation),
64 initialInstanceState: Pending,
65 }
66@@ -1060,6 +1088,70 @@
67 return &resp
68 }
69
70+func (srv *Server) createSubnet(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
71+ v := srv.vpc(req.Form.Get("VpcId"))
72+ cidrBlock := parseCidr(req.Form.Get("CidrBlock"))
73+ availZone := req.Form.Get("AvailabilityZone")
74+ if availZone == "" {
75+ // Assign one automatically as AWS does.
76+ availZone = "us-east-1b"
77+ }
78+ // calculate the available IP addresses, removing the first 4 and
79+ // the last, which are reserved by AWS. Since we already checked
80+ // the CIDR is valid, we don't check the error here.
81+ _, ipnet, _ := net.ParseCIDR(cidrBlock)
82+ maskOnes, maskBits := ipnet.Mask.Size()
83+ availIPs := 1<<uint(maskBits-maskOnes) - 5
84+
85+ srv.mu.Lock()
86+ defer srv.mu.Unlock()
87+ s := &subnet{ec2.Subnet{
88+ Id: fmt.Sprintf("subnet-%d", srv.subnetId.next()),
89+ VPCId: v.Id,
90+ State: ec2.AvailableState,
91+ CIDRBlock: cidrBlock,
92+ AvailZone: availZone,
93+ AvailableIPCount: availIPs,
94+ }}
95+ srv.subnets[s.Id] = s
96+ r := &ec2.CreateSubnetResp{
97+ RequestId: reqId,
98+ Subnet: s.Subnet,
99+ }
100+ return r
101+}
102+
103+func (srv *Server) deleteSubnet(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
104+ s := srv.subnet(req.Form.Get("SubnetId"))
105+ srv.mu.Lock()
106+ defer srv.mu.Unlock()
107+
108+ delete(srv.subnets, s.Id)
109+ return &ec2.SimpleResp{
110+ XMLName: xml.Name{"", "DeleteSubnetResponse"},
111+ RequestId: reqId,
112+ }
113+}
114+
115+func (srv *Server) describeSubnets(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
116+ srv.mu.Lock()
117+ defer srv.mu.Unlock()
118+
119+ idMap := collectIds(req.Form, "SubnetId.")
120+ f := newFilter(req.Form)
121+ var resp ec2.SubnetsResp
122+ resp.RequestId = reqId
123+ for _, s := range srv.subnets {
124+ ok, err := f.ok(s)
125+ if ok && (len(idMap) == 0 || idMap[s.Id]) {
126+ resp.Subnets = append(resp.Subnets, s.Subnet)
127+ } else if err != nil {
128+ fatalf(400, "InvalidParameterValue", "describe subnets: %v", err)
129+ }
130+ }
131+ return &resp
132+}
133+
134 func (r *reservation) hasRunningMachine() bool {
135 for _, inst := range r.instances {
136 if inst.state.Code != ShuttingDown.Code && inst.state.Code != Terminated.Code {
137@@ -1092,6 +1184,19 @@
138 return v
139 }
140
141+func (srv *Server) subnet(id string) *subnet {
142+ if id == "" {
143+ fatalf(400, "MissingParameter", "missing subnetId")
144+ }
145+ srv.mu.Lock()
146+ defer srv.mu.Unlock()
147+ s, found := srv.subnets[id]
148+ if !found {
149+ fatalf(400, "InvalidSubnetID.NotFound", "subnet %s not found", id)
150+ }
151+ return s
152+}
153+
154 // collectIds takes all values with the given prefix from form and
155 // returns a map with the ids as keys.
156 func collectIds(form url.Values, prefix string) map[string]bool {
157
158=== modified file 'ec2/responses_test.go'
159--- ec2/responses_test.go 2014-02-07 12:48:07 +0000
160+++ ec2/responses_test.go 2014-02-07 12:48:07 +0000
161@@ -629,3 +629,58 @@
162 </vpcSet>
163 </DescribeVpcsResponse>
164 `
165+
166+// http://goo.gl/wLPhf
167+var CreateSubnetExample = `
168+<CreateSubnetResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
169+ <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
170+ <subnet>
171+ <subnetId>subnet-9d4a7b6c</subnetId>
172+ <state>pending</state>
173+ <vpcId>vpc-1a2b3c4d</vpcId>
174+ <cidrBlock>10.0.1.0/24</cidrBlock>
175+ <availableIpAddressCount>251</availableIpAddressCount>
176+ <availabilityZone>us-east-1a</availabilityZone>
177+ <tagSet/>
178+ </subnet>
179+</CreateSubnetResponse>
180+`
181+
182+// http://goo.gl/KmhcBM
183+var DeleteSubnetExample = `
184+<DeleteSubnetResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
185+ <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
186+ <return>true</return>
187+</DeleteSubnetResponse>
188+`
189+
190+// http://goo.gl/NTKQVI
191+var DescribeSubnetsExample = `
192+<DescribeSubnetsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
193+ <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
194+ <subnetSet>
195+ <item>
196+ <subnetId>subnet-9d4a7b6c</subnetId>
197+ <state>available</state>
198+ <vpcId>vpc-1a2b3c4d</vpcId>
199+ <cidrBlock>10.0.1.0/24</cidrBlock>
200+ <availableIpAddressCount>251</availableIpAddressCount>
201+ <availabilityZone>us-east-1a</availabilityZone>
202+ <defaultForAz>false</defaultForAz>
203+ <mapPublicIpOnLaunch>false</mapPublicIpOnLaunch>
204+ <tagSet/>
205+ </item>
206+ <item>
207+ <subnetId>subnet-6e7f829e</subnetId>
208+ <state>available</state>
209+ <vpcId>vpc-1a2b3c4d</vpcId>
210+ <cidrBlock>10.0.0.0/24</cidrBlock>
211+ <availableIpAddressCount>251</availableIpAddressCount>
212+ <availabilityZone>us-east-1a</availabilityZone>
213+ <defaultForAz>false</defaultForAz>
214+ <mapPublicIpOnLaunch>false</mapPublicIpOnLaunch>
215+ <tagSet/>
216+ </item>
217+ </subnetSet>
218+</DescribeSubnetsResponse>
219+`
220
221=== added file 'ec2/subnets.go'
222--- ec2/subnets.go 1970-01-01 00:00:00 +0000
223+++ ec2/subnets.go 2014-02-07 12:48:07 +0000
224@@ -0,0 +1,111 @@
225+//
226+// goamz - Go packages to interact with the Amazon Web Services.
227+//
228+// https://wiki.ubuntu.com/goamz
229+//
230+// Copyright (c) 2014 Canonical Ltd.
231+//
232+// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
233+//
234+
235+package ec2
236+
237+import (
238+ "strconv"
239+)
240+
241+// Subnet describes an Amazon VPC subnet.
242+//
243+// See http://goo.gl/CdkvO2 for more details.
244+type Subnet struct {
245+ Id string `xml:"subnetId"`
246+ State string `xml:"state"`
247+ VPCId string `xml:"vpcId"`
248+ CIDRBlock string `xml:"cidrBlock"`
249+ AvailableIPCount int `xml:"availableIpAddressCount"`
250+ AvailZone string `xml:"availabilityZone"`
251+ DefaultForAZ bool `xml:"defaultForAz"`
252+ MapPublicIPOnLaunch bool `xml:"mapPublicIpOnLaunch"`
253+ Tags []Tag `xml:"tagSet>item"`
254+}
255+
256+// CreateSubnetResp is the response to a CreateSubnet request.
257+//
258+// See http://goo.gl/wLPhfI for more details.
259+type CreateSubnetResp struct {
260+ RequestId string `xml:"requestId"`
261+ Subnet Subnet `xml:"subnet"`
262+}
263+
264+// CreateSubnet creates a subnet in an existing VPC.
265+//
266+// The vpcId and cidrBlock parameters specify the VPC id and CIDR
267+// block respectively - these cannot be changed after creation. The
268+// subnet's CIDR block can be the same as the VPC's CIDR block
269+// (assuming you want only a single subnet in the VPC), or a subset of
270+// the VPC's CIDR block. If more than one subnet is created in a VPC,
271+// their CIDR blocks must not overlap. The smallest subnet (and VPC)
272+// that can be created uses a /28 netmask (16 IP addresses), and the
273+// largest uses a /16 netmask (65,536 IP addresses).
274+//
275+// availZone may be empty, in which case Amazon EC2 selects one for
276+// you (recommended).
277+//
278+// See http://goo.gl/wLPhfI for more details.
279+func (ec2 *EC2) CreateSubnet(vpcId, cidrBlock, availZone string) (resp *CreateSubnetResp, err error) {
280+ params := makeParamsVPC("CreateSubnet")
281+ params["VpcId"] = vpcId
282+ params["CidrBlock"] = cidrBlock
283+ if availZone != "" {
284+ params["AvailabilityZone"] = availZone
285+ }
286+ resp = &CreateSubnetResp{}
287+ err = ec2.query(params, resp)
288+ if err != nil {
289+ return nil, err
290+ }
291+ return resp, nil
292+}
293+
294+// DeleteSubnet deletes the specified subnet. All running instances in
295+// the subnet must have been terminated.
296+//
297+// See http://goo.gl/KmhcBM for more details.
298+func (ec2 *EC2) DeleteSubnet(id string) (resp *SimpleResp, err error) {
299+ params := makeParamsVPC("DeleteSubnet")
300+ params["SubnetId"] = id
301+ resp = &SimpleResp{}
302+ err = ec2.query(params, resp)
303+ if err != nil {
304+ return nil, err
305+ }
306+ return resp, nil
307+}
308+
309+// SubnetsResp is the response to a Subnets request.
310+//
311+// See http://goo.gl/NTKQVI for more details.
312+type SubnetsResp struct {
313+ RequestId string `xml:"requestId"`
314+ Subnets []Subnet `xml:"subnetSet>item"`
315+}
316+
317+// Subnets describes one or more of your subnets. Both parameters are
318+// optional, and if specified will limit the returned subnets to the
319+// matching ids or filtering rules.
320+//
321+// See http://goo.gl/NTKQVI for more details.
322+func (ec2 *EC2) Subnets(ids []string, filter *Filter) (resp *SubnetsResp, err error) {
323+ params := makeParamsVPC("DescribeSubnets")
324+ for i, id := range ids {
325+ params["SubnetId."+strconv.Itoa(i+1)] = id
326+ }
327+ filter.addParams(params)
328+
329+ resp = &SubnetsResp{}
330+ err = ec2.query(params, resp)
331+ if err != nil {
332+ return nil, err
333+ }
334+ return resp, nil
335+}
336
337=== added file 'ec2/subnets_test.go'
338--- ec2/subnets_test.go 1970-01-01 00:00:00 +0000
339+++ ec2/subnets_test.go 2014-02-07 12:48:07 +0000
340@@ -0,0 +1,212 @@
341+//
342+// goamz - Go packages to interact with the Amazon Web Services.
343+//
344+// https://wiki.ubuntu.com/goamz
345+//
346+// Copyright (c) 2014 Canonical Ltd.
347+//
348+// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
349+//
350+
351+package ec2_test
352+
353+import (
354+ "launchpad.net/goamz/aws"
355+ "launchpad.net/goamz/ec2"
356+ . "launchpad.net/gocheck"
357+ "time"
358+)
359+
360+// Subnet tests with example responses
361+
362+func (s *S) TestCreateSubnetExample(c *C) {
363+ testServer.Response(200, nil, CreateSubnetExample)
364+
365+ resp, err := s.ec2.CreateSubnet("vpc-1a2b3c4d", "10.0.1.0/24", "us-east-1a")
366+ req := testServer.WaitRequest()
367+
368+ c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSubnet"})
369+ c.Assert(req.Form["VpcId"], DeepEquals, []string{"vpc-1a2b3c4d"})
370+ c.Assert(req.Form["CidrBlock"], DeepEquals, []string{"10.0.1.0/24"})
371+ c.Assert(req.Form["AvailabilityZone"], DeepEquals, []string{"us-east-1a"})
372+
373+ c.Assert(err, IsNil)
374+ c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
375+ subnet := resp.Subnet
376+ c.Check(subnet.Id, Equals, "subnet-9d4a7b6c")
377+ c.Check(subnet.State, Equals, "pending")
378+ c.Check(subnet.VPCId, Equals, "vpc-1a2b3c4d")
379+ c.Check(subnet.CIDRBlock, Equals, "10.0.1.0/24")
380+ c.Check(subnet.AvailableIPCount, Equals, 251)
381+ c.Check(subnet.AvailZone, Equals, "us-east-1a")
382+ c.Check(subnet.Tags, HasLen, 0)
383+}
384+
385+func (s *S) TestDeleteSubnetExample(c *C) {
386+ testServer.Response(200, nil, DeleteSubnetExample)
387+
388+ resp, err := s.ec2.DeleteSubnet("subnet-id")
389+ req := testServer.WaitRequest()
390+
391+ c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteSubnet"})
392+ c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-id"})
393+
394+ c.Assert(err, IsNil)
395+ c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
396+}
397+
398+func (s *S) TestSubnetsExample(c *C) {
399+ testServer.Response(200, nil, DescribeSubnetsExample)
400+
401+ ids := []string{"subnet-9d4a7b6c", "subnet-6e7f829e"}
402+ resp, err := s.ec2.Subnets(ids, nil)
403+ req := testServer.WaitRequest()
404+
405+ c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeSubnets"})
406+ c.Assert(req.Form["SubnetId.1"], DeepEquals, []string{ids[0]})
407+ c.Assert(req.Form["SubnetId.2"], DeepEquals, []string{ids[1]})
408+
409+ c.Assert(err, IsNil)
410+ c.Assert(resp.RequestId, Equals, "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE")
411+ c.Check(resp.Subnets, HasLen, 2)
412+ subnet := resp.Subnets[0]
413+ c.Check(subnet.Id, Equals, "subnet-9d4a7b6c")
414+ c.Check(subnet.State, Equals, ec2.AvailableState)
415+ c.Check(subnet.VPCId, Equals, "vpc-1a2b3c4d")
416+ c.Check(subnet.CIDRBlock, Equals, "10.0.1.0/24")
417+ c.Check(subnet.AvailableIPCount, Equals, 251)
418+ c.Check(subnet.AvailZone, Equals, "us-east-1a")
419+ c.Check(subnet.DefaultForAZ, Equals, false)
420+ c.Check(subnet.MapPublicIPOnLaunch, Equals, false)
421+ c.Check(subnet.Tags, HasLen, 0)
422+ subnet = resp.Subnets[1]
423+ c.Check(subnet.Id, Equals, "subnet-6e7f829e")
424+ c.Check(subnet.State, Equals, ec2.AvailableState)
425+ c.Check(subnet.VPCId, Equals, "vpc-1a2b3c4d")
426+ c.Check(subnet.CIDRBlock, Equals, "10.0.0.0/24")
427+ c.Check(subnet.AvailableIPCount, Equals, 251)
428+ c.Check(subnet.AvailZone, Equals, "us-east-1a")
429+ c.Check(subnet.DefaultForAZ, Equals, false)
430+ c.Check(subnet.MapPublicIPOnLaunch, Equals, false)
431+ c.Check(subnet.Tags, HasLen, 0)
432+}
433+
434+// Subnet tests run against either a local test server or live on EC2.
435+
436+func (s *ServerTests) TestSubnets(c *C) {
437+ resp, err := s.ec2.CreateVPC("10.2.0.0/16", "")
438+ c.Assert(err, IsNil)
439+ vpcId := resp.VPC.Id
440+ defer s.deleteVPCs(c, []string{vpcId})
441+
442+ // It might take some time for the VPC to get created, so we need
443+ // to retry a few times when running against the EC2 servers.
444+ var resp1 *ec2.CreateSubnetResp
445+ testAttempt := aws.AttemptStrategy{
446+ Total: 2 * time.Minute,
447+ Delay: 5 * time.Second,
448+ }
449+ done := false
450+ for a := testAttempt.Start(); a.Next(); {
451+ resp1, err = s.ec2.CreateSubnet(vpcId, "10.2.1.0/24", "")
452+ if s.errorCode(err) == "InvalidVpcID.NotFound" {
453+ c.Logf("VPC %v not created yet; retrying", vpcId)
454+ continue
455+ }
456+ if err != nil {
457+ c.Logf("retrying; CreateSubnet returned: %v", err)
458+ continue
459+ }
460+ done = true
461+ break
462+ }
463+ if !done {
464+ c.Fatalf("timeout while waiting for VPC and subnet")
465+ }
466+ assertSubnet(c, resp1.Subnet, "", vpcId, "10.2.1.0/24")
467+ id1 := resp1.Subnet.Id
468+
469+ resp2, err := s.ec2.CreateSubnet(vpcId, "10.2.2.0/24", "")
470+ c.Assert(err, IsNil)
471+ assertSubnet(c, resp2.Subnet, "", vpcId, "10.2.2.0/24")
472+ id2 := resp2.Subnet.Id
473+
474+ // We only check for the subnets we just created, because the user
475+ // might have others in his account (when testing against the EC2
476+ // servers). In some cases it takes a short while until both
477+ // subnets are created, so we need to retry a few times to make
478+ // sure.
479+ var list *ec2.SubnetsResp
480+ done = false
481+ for a := testAttempt.Start(); a.Next(); {
482+ c.Logf("waiting for %v to be created", []string{id1, id2})
483+ list, err = s.ec2.Subnets(nil, nil)
484+ if err != nil {
485+ c.Logf("retrying; Subnets returned: %v", err)
486+ continue
487+ }
488+ found := 0
489+ for _, subnet := range list.Subnets {
490+ c.Logf("found subnet %v", subnet)
491+ switch subnet.Id {
492+ case id1:
493+ assertSubnet(c, subnet, id1, vpcId, resp1.Subnet.CIDRBlock)
494+ found++
495+ case id2:
496+ assertSubnet(c, subnet, id2, vpcId, resp2.Subnet.CIDRBlock)
497+ found++
498+ }
499+ if found == 2 {
500+ done = true
501+ break
502+ }
503+ }
504+ if done {
505+ c.Logf("all subnets were created")
506+ break
507+ }
508+ }
509+ if !done {
510+ c.Fatalf("timeout while waiting for subnets %v", []string{id1, id2})
511+ }
512+
513+ list, err = s.ec2.Subnets([]string{id1}, nil)
514+ c.Assert(err, IsNil)
515+ c.Assert(list.Subnets, HasLen, 1)
516+ assertSubnet(c, list.Subnets[0], id1, vpcId, resp1.Subnet.CIDRBlock)
517+
518+ f := ec2.NewFilter()
519+ f.Add("cidr", resp2.Subnet.CIDRBlock)
520+ list, err = s.ec2.Subnets(nil, f)
521+ c.Assert(err, IsNil)
522+ c.Assert(list.Subnets, HasLen, 1)
523+ assertSubnet(c, list.Subnets[0], id2, vpcId, resp2.Subnet.CIDRBlock)
524+
525+ _, err = s.ec2.DeleteSubnet(id1)
526+ c.Assert(err, IsNil)
527+ _, err = s.ec2.DeleteSubnet(id2)
528+ c.Assert(err, IsNil)
529+}
530+
531+func assertSubnet(c *C, obtained ec2.Subnet, expectId, expectVpcId, expectCidr string) {
532+ if expectId != "" {
533+ c.Check(obtained.Id, Equals, expectId)
534+ } else {
535+ c.Check(obtained.Id, Matches, `^subnet-[0-9a-f]+$`)
536+ }
537+ c.Check(obtained.State, Matches, "("+ec2.AvailableState+"|"+ec2.PendingState+")")
538+ if expectVpcId != "" {
539+ c.Check(obtained.VPCId, Equals, expectVpcId)
540+ } else {
541+ c.Check(obtained.VPCId, Matches, `^vpc-[0-9a-f]+$`)
542+ }
543+ if expectCidr != "" {
544+ c.Check(obtained.CIDRBlock, Equals, expectCidr)
545+ } else {
546+ c.Check(obtained.CIDRBlock, Matches, `^\d+\.\d+\.\d+\.\d+/\d+$`)
547+ }
548+ c.Check(obtained.AvailZone, Not(Equals), "")
549+ c.Check(obtained.AvailableIPCount, Not(Equals), 0)
550+ c.Check(obtained.DefaultForAZ, Equals, false)
551+ c.Check(obtained.MapPublicIPOnLaunch, Equals, false)
552+}
553
554=== modified file 'ec2/vpc_test.go'
555--- ec2/vpc_test.go 2014-02-07 12:48:07 +0000
556+++ ec2/vpc_test.go 2014-02-07 12:48:07 +0000
557@@ -149,6 +149,36 @@
558 c.Assert(err, IsNil)
559 }
560
561+// deleteVPCs ensures the given VPCs are deleted, by retrying until a
562+// timeout or all VPC cannot be found anymore. This should be used to
563+// make sure tests leave no VPCs around.
564+func (s *ServerTests) deleteVPCs(c *C, ids []string) {
565+ testAttempt := aws.AttemptStrategy{
566+ Total: 2 * time.Minute,
567+ Delay: 5 * time.Second,
568+ }
569+ for a := testAttempt.Start(); a.Next(); {
570+ deleted := 0
571+ c.Logf("deleting VPCs %v", ids)
572+ for _, id := range ids {
573+ _, err := s.ec2.DeleteVPC(id)
574+ if err == nil || s.errorCode(err) == "InvalidVpcID.NotFound" {
575+ c.Logf("VPC %s deleted", id)
576+ deleted++
577+ continue
578+ }
579+ if err != nil {
580+ c.Logf("retrying; DeleteVPC returned: %v", err)
581+ }
582+ }
583+ if deleted == len(ids) {
584+ c.Logf("all VPCs deleted")
585+ return
586+ }
587+ }
588+ c.Fatalf("timeout while waiting %v VPCs to get deleted!", ids)
589+}
590+
591 func assertVPC(c *C, obtained ec2.VPC, expectId, expectCidr string) {
592 if expectId != "" {
593 c.Check(obtained.Id, Equals, expectId)

Subscribers

People subscribed via source and target branches