Merge lp:~dimitern/goamz/subnets-api-calls into lp:goamz
- subnets-api-calls
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dimiter Naydenov (community) | Approve | ||
Review via email: mp+204640@code.launchpad.net |
Commit message
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).
Dimiter Naydenov (dimitern) wrote : | # |
- 51. By Dimiter Naydenov
-
Minor fixes
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
- 52. By Dimiter Naydenov
-
Merged conflicts; simplified & refactored a bit; naming fixed, API version updated
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Roger Peppe (rogpeppe) wrote : | # |
LGTM with some suggestions below.
https:/
File ec2/ec2test/
https:/
ec2/ec2test/
int(math.
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:/
File ec2/subnets.go (right):
https:/
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:/
ec2/subnets.go:52: // availZone can be empty, in which case Amazon EC2
selects one for
s/can/may/
https:/
ec2/subnets.go:71: // DeleteSubnet deletes the specified subnet. You
must terminate all
// All running instances in the subnet must have been terminated.
?
- 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
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
https:/
File ec2/ec2test/
https:/
ec2/ec2test/
int(math.
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:/
File ec2/subnets.go (right):
https:/
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:/
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:/
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.
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Martin Packman (gz) wrote : | # |
LGTM.
https:/
File ec2/subnets.go (right):
https:/
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:/
File ec2/subnets_test.go (right):
https:/
ec2/subnets_
Hm, this looks familiar. :)
At third one, should figure out how to make a helper for this.
https:/
File ec2/vpc_test.go (right):
https:/
ec2/vpc_
[]string) {
Somewher later in the pipe this gets picked up for the existing vpc live
test too?
- 56. By Dimiter Naydenov
-
Merged vpc-api-calls into subnets-api-calls.
- 57. By Dimiter Naydenov
-
Changes after reivew
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
https:/
File ec2/subnets.go (right):
https:/
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:/
File ec2/subnets_test.go (right):
https:/
ec2/subnets_
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:/
File ec2/vpc_test.go (right):
https:/
ec2/vpc_
[]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.
Gustavo Niemeyer (niemeyer) wrote : | # |
LGTM
https:/
File ec2/subnets.go (right):
https:/
ec2/subnets.go:8: // Written by Gustavo Niemeyer
<email address hidden>
Please drop this line.
https:/
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:/
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:/
ec2/subnets.go:93: // Subnets describes one or more of your subnets.
Both parameters are
// Subnets returns one or more subnets. Both ...
https:/
File ec2/subnets_test.go (right):
https:/
ec2/subnets_
<email address hidden>
Please drop this line.
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:/
https:/
File ec2/subnets.go (right):
https:/
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:/
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:/
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:/
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:/
File ec2/subnets_test.go (right):
https:/
ec2/subnets_
<email address hidden>
On 2014/02/12 13:33:51, niemeyer wrote:
> Please drop this line.
Done.
Dimiter Naydenov (dimitern) wrote : | # |
Post-submit approval.
Preview Diff
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) |
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: /code.launchpad .net/~dimitern/ goamz/vpc- api-calls/ +merge/ 204514
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/54690048/
Affected files (+466, -2 lines): server. go test.go
A [revision details]
M ec2/ec2test/
M ec2/responses_
A ec2/subnets.go
A ec2/subnets_test.go