Merge lp:~dimitern/goamz/nic-api-calls into lp:goamz
- nic-api-calls
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 48 |
Proposed branch: | lp:~dimitern/goamz/nic-api-calls |
Merge into: | lp:goamz |
Prerequisite: | lp:~dimitern/goamz/subnets-api-calls |
Diff against target: |
1298 lines (+1086/-41) 8 files modified
ec2/ec2.go (+16/-3) ec2/ec2_test.go (+21/-11) ec2/ec2t_test.go (+5/-1) ec2/ec2test/server.go (+243/-0) ec2/networkinterfaces.go (+223/-0) ec2/networkinterfaces_test.go (+367/-0) ec2/responses_test.go (+148/-0) ec2/subnets_test.go (+63/-26) |
To merge this branch: | bzr merge lp:~dimitern/goamz/nic-api-calls |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dimiter Naydenov (community) | Approve | ||
Review via email: mp+204912@code.launchpad.net |
Commit message
Description of the change
ec2: Add VPC NetworkInterfac
Added the following new API calls:
- CreateNetworkIn
- DeleteNetworkIn
- NetworkInterfaces
- AttachNetworkIn
- DetachNetworkIn
(and related types/responses)
Modified existing calls/types:
- SecurityGroupInfo now includes VPCId field
- Add CreateSecurityG
the same as CreateSecurityG
the VPCId, to create a VPC group
This enables us to handle VPC NICs with
goamz and partially handle private IP
addresses for them. Next, we'll add more
VPC-related stuff to Instances.
Tested live on EC2, extended ec2test package
as needed.
Added a couple of test helpers: createSubnet,
and deleteSubnets, used to make sure we wait
for the events to happen when running against
live EC2 servers, and we don't leave stuff
around after the tests. With these the live
tests pass and clean up properly.
Dimiter Naydenov (dimitern) wrote : | # |
- 54. By Dimiter Naydenov
-
Fixed a test slightly
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Roger Peppe (rogpeppe) wrote : | # |
LGTM with some minor suggestions below.
Thanks!
https:/
File ec2/ec2.go (right):
https:/
ec2/ec2.go:635: // CreateSecurityGroup run a CreateSecurityGroup request
in EC2, with
s/run/runs/
https:/
ec2/ec2.go:643: // CreateSecurityG
EC2, associated
s/,//
// If vpcId is empty, this call is equivalent to CreateSecurityG
?
https:/
File ec2/networkinte
https:/
ec2/networkinte
address by setting
Why have we got both PrivateIPs and PrivateIPAddress?
Couldn't we have PrivateIPs only and avoid the question
of what happens when they're both set?
https:/
ec2/networkinte
address, EC2 selects one for you
// If a private IP address is not specified, EC2 selects
// one from the subnet range.
https:/
ec2/networkinte
of secondary private IP
// When SecondaryPrivat
// EC2 allocates that number of
// IP addresses from within the subnet range.
// The number of IP addresses you
// can assign to a network interface varies by instance type.
?
https:/
ec2/networkinte
before you can delete it.
// DeleteNetworkIn
// which must have been previously detached.
?
https:/
ec2/networkinte
response to a
s/ a/ an/
- 55. By Dimiter Naydenov
-
Merged parent
- 56. By Dimiter Naydenov
-
Merged subnets-api-calls into nic-api-calls.
- 57. By Dimiter Naydenov
-
Changes after review; tests improvements
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
https:/
File ec2/ec2.go (right):
https:/
ec2/ec2.go:635: // CreateSecurityGroup run a CreateSecurityGroup request
in EC2, with
On 2014/02/05 15:17:35, rog wrote:
> s/run/runs/
Done.
https:/
ec2/ec2.go:643: // CreateSecurityG
EC2, associated
On 2014/02/05 15:17:35, rog wrote:
> s/,//
> // If vpcId is empty, this call is equivalent to CreateSecurityG
> ?
Done.
https:/
File ec2/networkinte
https:/
ec2/networkinte
address by setting
On 2014/02/05 15:17:35, rog wrote:
> Why have we got both PrivateIPs and PrivateIPAddress?
> Couldn't we have PrivateIPs only and avoid the question
> of what happens when they're both set?
I tried to stay close to the AWS API, which allows both but with some
conditions. Having PrivateIPAddress and setting just that is a shortcut
to setting PrivateIPs: []ec2.PrivateIP
true}}. But perhaps the slightly more verbose syntax will work well to
hide the API complexity.
https:/
ec2/networkinte
address, EC2 selects one for you
On 2014/02/05 15:17:35, rog wrote:
> // If a private IP address is not specified, EC2 selects
> // one from the subnet range.
Done slightly differently.
https:/
ec2/networkinte
of secondary private IP
On 2014/02/05 15:17:35, rog wrote:
> // When SecondaryPrivat
> // EC2 allocates that number of
> // IP addresses from within the subnet range.
> // The number of IP addresses you
> // can assign to a network interface varies by instance type.
> ?
Done.
https:/
ec2/networkinte
before you can delete it.
On 2014/02/05 15:17:35, rog wrote:
> // DeleteNetworkIn
> // which must have been previously detached.
> ?
Done.
https:/
ec2/networkinte
response to a
On 2014/02/05 15:17:35, rog wrote:
> s/ a/ an/
Done.
- 58. By Dimiter Naydenov
-
Merged subnets-api-calls into nic-api-calls.
- 59. By Dimiter Naydenov
-
Changes after reivew
- 60. By Dimiter Naydenov
-
Merge fixes
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Gustavo Niemeyer (niemeyer) wrote : | # |
LGTM
https:/
File ec2/ec2.go (right):
https:/
ec2/ec2.go:637: // the provided name and description.
Would you mind to update this to be slightly more sane:
// CreateSecurityGroup creates a security group with the provided name
// and description.
https:/
File ec2/networkinte
https:/
ec2/networkinte
<email address hidden>
Please drop this line.
https:/
ec2/networkinte
interface for AWS VPC.
s/ AWS//
https:/
ec2/networkinte
are optional.
// SubnetId is the only required field.
This already implies that the rest is optional.
https:/
ec2/networkinte
be set as primary.
// Only one provided PrivateIP may be set as primary.
It's implicit that one or more may be provided.
https:/
ec2/networkinte
s/IPsCount/
plural form used by the underlying EC2 parameter.
https:/
ec2/networkinte
filtering rules.
// NetworkInterfaces returns a list of network interfaces.
//
// If the ids or filter parameters are provided, only matching
interfaces
// are returned.
https:/
File ec2/subnets_test.go (right):
https:/
ec2/subnets_
test server or live on EC2.
s/ to// -- the original sentence was correct ("These tests run
against...")
Dimiter Naydenov (dimitern) wrote : | # |
*** Submitted:
ec2: Add VPC NetworkInterfac
Added the following new API calls:
- CreateNetworkIn
- DeleteNetworkIn
- NetworkInterfaces
- AttachNetworkIn
- DetachNetworkIn
(and related types/responses)
Modified existing calls/types:
- SecurityGroupInfo now includes VPCId field
- Add CreateSecurityG
the same as CreateSecurityG
the VPCId, to create a VPC group
This enables us to handle VPC NICs with
goamz and partially handle private IP
addresses for them. Next, we'll add more
VPC-related stuff to Instances.
Tested live on EC2, extended ec2test package
as needed.
Added a couple of test helpers: createSubnet,
and deleteSubnets, used to make sure we wait
for the events to happen when running against
live EC2 servers, and we don't leave stuff
around after the tests. With these the live
tests pass and clean up properly.
R=rog, niemeyer
CC=
https:/
https:/
File ec2/ec2.go (right):
https:/
ec2/ec2.go:637: // the provided name and description.
On 2014/02/12 14:22:38, niemeyer wrote:
> Would you mind to update this to be slightly more sane:
> // CreateSecurityGroup creates a security group with the provided name
> // and description.
Done.
https:/
File ec2/networkinte
https:/
ec2/networkinte
<email address hidden>
On 2014/02/12 14:22:38, niemeyer wrote:
> Please drop this line.
Done.
https:/
ec2/networkinte
interface for AWS VPC.
On 2014/02/12 14:22:38, niemeyer wrote:
> s/ AWS//
Done.
https:/
ec2/networkinte
are optional.
On 2014/02/12 14:22:38, niemeyer wrote:
> // SubnetId is the only required field.
> This already implies that the rest is optional.
Done.
https:/
ec2/networkinte
be set as primary.
On 2014/02/12 14:22:38, niemeyer wrote:
> // Only one provided PrivateIP may be set as primary.
> It's implicit that one or more may be provided.
Done.
https:/
ec2/networkinte
On 2014/02/12 14:22:38, niemeyer wrote:
> s/IPsCount/
plural
> form used by the underlying EC2 parameter.
Done. Also, as agreed on IRC, I'll rename NetworkInterfac
CreateNetworkIn
https:/
ec2/networkinte
filter...
Dimiter Naydenov (dimitern) wrote : | # |
Post-submit approval.
Preview Diff
1 | === modified file 'ec2/ec2.go' |
2 | --- ec2/ec2.go 2014-02-07 12:56:14 +0000 |
3 | +++ ec2/ec2.go 2014-02-07 12:56:14 +0000 |
4 | @@ -633,14 +633,26 @@ |
5 | RequestId string `xml:"requestId"` |
6 | } |
7 | |
8 | -// CreateSecurityGroup run a CreateSecurityGroup request in EC2, with the provided |
9 | -// name and description. |
10 | +// CreateSecurityGroup runs a CreateSecurityGroup request in EC2, with |
11 | +// the provided name and description. |
12 | // |
13 | // See http://goo.gl/Eo7Yl for more details. |
14 | func (ec2 *EC2) CreateSecurityGroup(name, description string) (resp *CreateSecurityGroupResp, err error) { |
15 | - params := makeParams("CreateSecurityGroup") |
16 | + return ec2.CreateSecurityGroupVPC("", name, description) |
17 | +} |
18 | + |
19 | +// CreateSecurityGroupVPC creates a security group in EC2, associated |
20 | +// with the given VPC ID. If vpcId is empty, this call is equivalent |
21 | +// to CreateSecurityGroup. |
22 | +// |
23 | +// See http://goo.gl/Eo7Yl for more details. |
24 | +func (ec2 *EC2) CreateSecurityGroupVPC(vpcId, name, description string) (resp *CreateSecurityGroupResp, err error) { |
25 | + params := makeParamsVPC("CreateSecurityGroup") |
26 | params["GroupName"] = name |
27 | params["GroupDescription"] = description |
28 | + if vpcId != "" { |
29 | + params["VpcId"] = vpcId |
30 | + } |
31 | |
32 | resp = &CreateSecurityGroupResp{} |
33 | err = ec2.query(params, resp) |
34 | @@ -665,6 +677,7 @@ |
35 | // See http://goo.gl/CIdyP for more details. |
36 | type SecurityGroupInfo struct { |
37 | SecurityGroup |
38 | + VPCId string `xml:"vpcId"` |
39 | OwnerId string `xml:"ownerId"` |
40 | Description string `xml:"groupDescription"` |
41 | IPPerms []IPPerm `xml:"ipPermissions>item"` |
42 | |
43 | === modified file 'ec2/ec2_test.go' |
44 | --- ec2/ec2_test.go 2014-02-07 12:56:14 +0000 |
45 | +++ ec2/ec2_test.go 2014-02-07 12:56:14 +0000 |
46 | @@ -400,20 +400,30 @@ |
47 | c.Assert(s0.Tags[0].Value, Equals, "demo_db_14_backup") |
48 | } |
49 | |
50 | +func (s *S) checkCreateSGResponse(c *C, resp *ec2.CreateSecurityGroupResp, id, name, description, vpcId string) { |
51 | + req := testServer.WaitRequest() |
52 | + c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSecurityGroup"}) |
53 | + c.Assert(req.Form["GroupName"], DeepEquals, []string{name}) |
54 | + c.Assert(req.Form["GroupDescription"], DeepEquals, []string{description}) |
55 | + if vpcId != "" { |
56 | + c.Assert(req.Form["VpcId"], DeepEquals, []string{vpcId}) |
57 | + } |
58 | + |
59 | + c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE") |
60 | + c.Assert(resp.Name, Equals, name) |
61 | + c.Assert(resp.Id, Equals, id) |
62 | +} |
63 | + |
64 | func (s *S) TestCreateSecurityGroupExample(c *C) { |
65 | testServer.Response(200, nil, CreateSecurityGroupExample) |
66 | - |
67 | resp, err := s.ec2.CreateSecurityGroup("websrv", "Web Servers") |
68 | - |
69 | - req := testServer.WaitRequest() |
70 | - c.Assert(req.Form["Action"], DeepEquals, []string{"CreateSecurityGroup"}) |
71 | - c.Assert(req.Form["GroupName"], DeepEquals, []string{"websrv"}) |
72 | - c.Assert(req.Form["GroupDescription"], DeepEquals, []string{"Web Servers"}) |
73 | - |
74 | - c.Assert(err, IsNil) |
75 | - c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE") |
76 | - c.Assert(resp.Name, Equals, "websrv") |
77 | - c.Assert(resp.Id, Equals, "sg-67ad940e") |
78 | + c.Assert(err, IsNil) |
79 | + s.checkCreateSGResponse(c, resp, "sg-67ad940e", "websrv", "Web Servers", "") |
80 | + |
81 | + testServer.Response(200, nil, CreateSecurityGroupExample) |
82 | + resp, err = s.ec2.CreateSecurityGroupVPC("vpc-id", "websrv", "Web Servers") |
83 | + c.Assert(err, IsNil) |
84 | + s.checkCreateSGResponse(c, resp, "sg-67ad940e", "websrv", "Web Servers", "vpc-id") |
85 | } |
86 | |
87 | func (s *S) TestDescribeSecurityGroupsExample(c *C) { |
88 | |
89 | === modified file 'ec2/ec2t_test.go' |
90 | --- ec2/ec2t_test.go 2014-02-07 12:56:14 +0000 |
91 | +++ ec2/ec2t_test.go 2014-02-07 12:56:14 +0000 |
92 | @@ -148,13 +148,17 @@ |
93 | } |
94 | |
95 | func (s *ServerTests) makeTestGroup(c *C, name, descr string) ec2.SecurityGroup { |
96 | + return s.makeTestGroupVPC(c, "", name, descr) |
97 | +} |
98 | + |
99 | +func (s *ServerTests) makeTestGroupVPC(c *C, vpcId, name, descr string) ec2.SecurityGroup { |
100 | // Clean it up if a previous test left it around. |
101 | _, err := s.ec2.DeleteSecurityGroup(ec2.SecurityGroup{Name: name}) |
102 | if err != nil && s.errorCode(err) != "InvalidGroup.NotFound" { |
103 | c.Fatalf("delete security group: %v", err) |
104 | } |
105 | |
106 | - resp, err := s.ec2.CreateSecurityGroup(name, descr) |
107 | + resp, err := s.ec2.CreateSecurityGroupVPC(vpcId, name, descr) |
108 | c.Assert(err, IsNil) |
109 | c.Assert(resp.Name, Equals, name) |
110 | return resp.SecurityGroup |
111 | |
112 | === modified file 'ec2/ec2test/server.go' |
113 | --- ec2/ec2test/server.go 2014-02-07 12:56:14 +0000 |
114 | +++ ec2/ec2test/server.go 2014-02-07 12:56:14 +0000 |
115 | @@ -17,6 +17,7 @@ |
116 | "strconv" |
117 | "strings" |
118 | "sync" |
119 | + "time" |
120 | ) |
121 | |
122 | var b64 = base64.StdEncoding |
123 | @@ -52,6 +53,8 @@ |
124 | groups map[string]*securityGroup // id -> group |
125 | vpcs map[string]*vpc // id -> vpc |
126 | subnets map[string]*subnet // id -> subnet |
127 | + ifaces map[string]*iface // id -> iface |
128 | + attachments map[string]*attachment // id -> attachment |
129 | maxId counter |
130 | reqId counter |
131 | reservationId counter |
132 | @@ -59,6 +62,8 @@ |
133 | vpcId counter |
134 | dhcpOptsId counter |
135 | subnetId counter |
136 | + ifaceId counter |
137 | + attachId counter |
138 | initialInstanceState ec2.InstanceState |
139 | } |
140 | |
141 | @@ -103,6 +108,7 @@ |
142 | id string |
143 | name string |
144 | description string |
145 | + vpcId string |
146 | |
147 | perms map[permKey]bool |
148 | } |
149 | @@ -236,6 +242,41 @@ |
150 | return false, fmt.Errorf("unknown attribute %q", attr) |
151 | } |
152 | |
153 | +type iface struct { |
154 | + ec2.NetworkInterface |
155 | +} |
156 | + |
157 | +func (i *iface) matchAttr(attr, value string) (ok bool, err error) { |
158 | + notImplemented := []string{ |
159 | + "addresses.", "association.", "tag", "requester-", |
160 | + "attachment.", "source-dest-check", "mac-address", |
161 | + "group-", "description", "private-", "owner-id", |
162 | + } |
163 | + switch attr { |
164 | + case "availability-zone": |
165 | + return i.AvailZone == value, nil |
166 | + case "network-interface-id": |
167 | + return i.Id == value, nil |
168 | + case "status": |
169 | + return i.Status == value, nil |
170 | + case "subnet-id": |
171 | + return i.SubnetId == value, nil |
172 | + case "vpc-id": |
173 | + return i.VPCId == value, nil |
174 | + default: |
175 | + for _, item := range notImplemented { |
176 | + if strings.HasPrefix(attr, item) { |
177 | + return false, fmt.Errorf("%q filter not implemented", attr) |
178 | + } |
179 | + } |
180 | + } |
181 | + return false, fmt.Errorf("unknown attribute %q", attr) |
182 | +} |
183 | + |
184 | +type attachment struct { |
185 | + ec2.NetworkInterfaceAttachment |
186 | +} |
187 | + |
188 | var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) interface{}{ |
189 | "RunInstances": (*Server).runInstances, |
190 | "TerminateInstances": (*Server).terminateInstances, |
191 | @@ -251,6 +292,11 @@ |
192 | "CreateSubnet": (*Server).createSubnet, |
193 | "DeleteSubnet": (*Server).deleteSubnet, |
194 | "DescribeSubnets": (*Server).describeSubnets, |
195 | + "CreateNetworkInterface": (*Server).createIFace, |
196 | + "DeleteNetworkInterface": (*Server).deleteIFace, |
197 | + "DescribeNetworkInterfaces": (*Server).describeIFaces, |
198 | + "AttachNetworkInterface": (*Server).attachIFace, |
199 | + "DetachNetworkInterface": (*Server).detachIFace, |
200 | } |
201 | |
202 | const ownerId = "9876" |
203 | @@ -273,6 +319,8 @@ |
204 | groups: make(map[string]*securityGroup), |
205 | vpcs: make(map[string]*vpc), |
206 | subnets: make(map[string]*subnet), |
207 | + ifaces: make(map[string]*iface), |
208 | + attachments: make(map[string]*attachment), |
209 | reservations: make(map[string]*reservation), |
210 | initialInstanceState: Pending, |
211 | } |
212 | @@ -1152,6 +1200,158 @@ |
213 | return &resp |
214 | } |
215 | |
216 | +func (srv *Server) createIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} { |
217 | + s := srv.subnet(req.Form.Get("SubnetId")) |
218 | + ipMap := make(map[int]ec2.PrivateIP) |
219 | + primaryIP := req.Form.Get("PrivateIpAddress") |
220 | + if primaryIP != "" { |
221 | + ipMap[0] = ec2.PrivateIP{Address: primaryIP, IsPrimary: true} |
222 | + } |
223 | + desc := req.Form.Get("Description") |
224 | + |
225 | + var groups []ec2.SecurityGroup |
226 | + for name, vals := range req.Form { |
227 | + if strings.HasPrefix(name, "SecurityGroupId.") { |
228 | + g := ec2.SecurityGroup{Id: vals[0]} |
229 | + sg := srv.group(g) |
230 | + if sg == nil { |
231 | + fatalf(400, "InvalidGroup.NotFound", "no such group %v", g) |
232 | + } |
233 | + groups = append(groups, sg.ec2SecurityGroup()) |
234 | + } |
235 | + if strings.HasPrefix(name, "PrivateIpAddresses.") { |
236 | + var ip ec2.PrivateIP |
237 | + parts := strings.Split(name, ".") |
238 | + index := atoi(parts[1]) - 1 |
239 | + if index < 0 { |
240 | + fatalf(400, "InvalidParameterValue", "invalid index %s", name) |
241 | + } |
242 | + if _, ok := ipMap[index]; ok { |
243 | + ip = ipMap[index] |
244 | + } |
245 | + switch parts[2] { |
246 | + case "PrivateIpAddress": |
247 | + ip.Address = vals[0] |
248 | + case "Primary": |
249 | + val, err := strconv.ParseBool(vals[0]) |
250 | + if err != nil { |
251 | + fatalf(400, "InvalidParameterValue", "bad flag %s: %s", name, vals[0]) |
252 | + } |
253 | + ip.IsPrimary = val |
254 | + } |
255 | + ipMap[index] = ip |
256 | + } |
257 | + } |
258 | + privateIPs := make([]ec2.PrivateIP, len(ipMap)) |
259 | + for index, ip := range ipMap { |
260 | + if ip.IsPrimary { |
261 | + primaryIP = ip.Address |
262 | + } |
263 | + privateIPs[index] = ip |
264 | + } |
265 | + |
266 | + srv.mu.Lock() |
267 | + defer srv.mu.Unlock() |
268 | + i := &iface{ec2.NetworkInterface{ |
269 | + Id: fmt.Sprintf("eni-%d", srv.ifaceId.next()), |
270 | + SubnetId: s.Id, |
271 | + VPCId: s.VPCId, |
272 | + AvailZone: s.AvailZone, |
273 | + Description: desc, |
274 | + OwnerId: ownerId, |
275 | + Status: ec2.AvailableStatus, |
276 | + MACAddress: fmt.Sprintf("%02d:81:60:cb:27:37", srv.ifaceId), |
277 | + PrivateIPAddress: primaryIP, |
278 | + SourceDestCheck: true, |
279 | + Groups: groups, |
280 | + PrivateIPs: privateIPs, |
281 | + }} |
282 | + srv.ifaces[i.Id] = i |
283 | + r := &ec2.CreateNetworkInterfaceResp{ |
284 | + RequestId: reqId, |
285 | + NetworkInterface: i.NetworkInterface, |
286 | + } |
287 | + return r |
288 | +} |
289 | + |
290 | +func (srv *Server) deleteIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} { |
291 | + i := srv.iface(req.Form.Get("NetworkInterfaceId")) |
292 | + |
293 | + srv.mu.Lock() |
294 | + defer srv.mu.Unlock() |
295 | + |
296 | + delete(srv.ifaces, i.Id) |
297 | + return &ec2.SimpleResp{ |
298 | + XMLName: xml.Name{"", "DeleteNetworkInterface"}, |
299 | + RequestId: reqId, |
300 | + } |
301 | +} |
302 | + |
303 | +func (srv *Server) describeIFaces(w http.ResponseWriter, req *http.Request, reqId string) interface{} { |
304 | + srv.mu.Lock() |
305 | + defer srv.mu.Unlock() |
306 | + |
307 | + idMap := collectIds(req.Form, "NetworkInterfaceId.") |
308 | + f := newFilter(req.Form) |
309 | + var resp ec2.NetworkInterfacesResp |
310 | + resp.RequestId = reqId |
311 | + for _, i := range srv.ifaces { |
312 | + ok, err := f.ok(i) |
313 | + if ok && (len(idMap) == 0 || idMap[i.Id]) { |
314 | + resp.Interfaces = append(resp.Interfaces, i.NetworkInterface) |
315 | + } else if err != nil { |
316 | + fatalf(400, "InvalidParameterValue", "describe ifaces: %v", err) |
317 | + } |
318 | + } |
319 | + return &resp |
320 | +} |
321 | + |
322 | +func (srv *Server) attachIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} { |
323 | + i := srv.iface(req.Form.Get("NetworkInterfaceId")) |
324 | + inst := srv.instance(req.Form.Get("InstanceId")) |
325 | + devIndex := atoi(req.Form.Get("DeviceIndex")) |
326 | + |
327 | + srv.mu.Lock() |
328 | + defer srv.mu.Unlock() |
329 | + a := &attachment{ec2.NetworkInterfaceAttachment{ |
330 | + Id: fmt.Sprintf("eni-attach-%d", srv.attachId.next()), |
331 | + InstanceId: inst.id(), |
332 | + InstanceOwnerId: ownerId, |
333 | + DeviceIndex: devIndex, |
334 | + Status: ec2.InUseStatus, |
335 | + AttachTime: time.Now().Format(time.RFC3339), |
336 | + DeleteOnTermination: true, |
337 | + }} |
338 | + srv.attachments[a.Id] = a |
339 | + i.Attachment = a.NetworkInterfaceAttachment |
340 | + srv.ifaces[i.Id] = i |
341 | + r := &ec2.AttachNetworkInterfaceResp{ |
342 | + RequestId: reqId, |
343 | + AttachmentId: a.Id, |
344 | + } |
345 | + return r |
346 | +} |
347 | + |
348 | +func (srv *Server) detachIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} { |
349 | + att := srv.attachment(req.Form.Get("AttachmentId")) |
350 | + |
351 | + srv.mu.Lock() |
352 | + defer srv.mu.Unlock() |
353 | + |
354 | + for _, i := range srv.ifaces { |
355 | + if i.Attachment.Id == att.Id { |
356 | + i.Attachment = ec2.NetworkInterfaceAttachment{} |
357 | + srv.ifaces[i.Id] = i |
358 | + break |
359 | + } |
360 | + } |
361 | + delete(srv.attachments, att.Id) |
362 | + return &ec2.SimpleResp{ |
363 | + XMLName: xml.Name{"", "DetachNetworkInterface"}, |
364 | + RequestId: reqId, |
365 | + } |
366 | +} |
367 | + |
368 | func (r *reservation) hasRunningMachine() bool { |
369 | for _, inst := range r.instances { |
370 | if inst.state.Code != ShuttingDown.Code && inst.state.Code != Terminated.Code { |
371 | @@ -1197,6 +1397,49 @@ |
372 | return s |
373 | } |
374 | |
375 | +func (srv *Server) iface(id string) *iface { |
376 | + if id == "" { |
377 | + fatalf(400, "MissingParameter", "missing networkInterfaceId") |
378 | + } |
379 | + srv.mu.Lock() |
380 | + defer srv.mu.Unlock() |
381 | + i, found := srv.ifaces[id] |
382 | + if !found { |
383 | + fatalf(400, "InvalidNetworkInterfaceID.NotFound", "interface %s not found", id) |
384 | + } |
385 | + return i |
386 | +} |
387 | + |
388 | +func (srv *Server) instance(id string) *Instance { |
389 | + if id == "" { |
390 | + fatalf(400, "MissingParameter", "missing instanceId") |
391 | + } |
392 | + srv.mu.Lock() |
393 | + defer srv.mu.Unlock() |
394 | + inst, found := srv.instances[id] |
395 | + if !found { |
396 | + fatalf(400, "InvalidInstanceID.NotFound", "instance %s not found", id) |
397 | + } |
398 | + return inst |
399 | +} |
400 | + |
401 | +func (srv *Server) attachment(id string) *attachment { |
402 | + if id == "" { |
403 | + fatalf(400, "MissingParameter", "missing attachmentId") |
404 | + } |
405 | + srv.mu.Lock() |
406 | + defer srv.mu.Unlock() |
407 | + att, found := srv.attachments[id] |
408 | + if !found { |
409 | + fatalf( |
410 | + 400, |
411 | + "InvalidNetworkInterfaceAttachmentId.NotFound", |
412 | + "attachment %s not found", id, |
413 | + ) |
414 | + } |
415 | + return att |
416 | +} |
417 | + |
418 | // collectIds takes all values with the given prefix from form and |
419 | // returns a map with the ids as keys. |
420 | func collectIds(form url.Values, prefix string) map[string]bool { |
421 | |
422 | === added file 'ec2/networkinterfaces.go' |
423 | --- ec2/networkinterfaces.go 1970-01-01 00:00:00 +0000 |
424 | +++ ec2/networkinterfaces.go 2014-02-07 12:56:14 +0000 |
425 | @@ -0,0 +1,223 @@ |
426 | +// |
427 | +// goamz - Go packages to interact with the Amazon Web Services. |
428 | +// |
429 | +// https://wiki.ubuntu.com/goamz |
430 | +// |
431 | +// Copyright (c) 2014 Canonical Ltd. |
432 | +// |
433 | +// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com> |
434 | +// |
435 | + |
436 | +package ec2 |
437 | + |
438 | +import ( |
439 | + "fmt" |
440 | + "strconv" |
441 | +) |
442 | + |
443 | +// NetworkInterfaceAttachment describes a network interface |
444 | +// attachment. |
445 | +// |
446 | +// See http://goo.gl/KtiKuV for more details. |
447 | +type NetworkInterfaceAttachment struct { |
448 | + Id string `xml:"attachmentId"` |
449 | + InstanceId string `xml:"instanceId"` |
450 | + InstanceOwnerId string `xml:"instanceOwnerId"` |
451 | + DeviceIndex int `xml:"deviceIndex"` |
452 | + Status string `xml:"status"` |
453 | + AttachTime string `xml:"attachTime"` |
454 | + DeleteOnTermination bool `xml:"deleteOnTermination"` |
455 | +} |
456 | + |
457 | +const ( |
458 | + // Common status values for network interfaces / attachments. |
459 | + AvailableStatus = "available" |
460 | + AttachingStatus = "attaching" |
461 | + AttachedStatus = "attached" |
462 | + PendingStatus = "pending" |
463 | + InUseStatus = "in-use" |
464 | + DetachingStatus = "detaching" |
465 | + DetachedStatus = "detached" |
466 | +) |
467 | + |
468 | +// PrivateIP describes a private IP address of a network interface. |
469 | +// |
470 | +// See http://goo.gl/jtuQEJ for more details. |
471 | +type PrivateIP struct { |
472 | + Address string `xml:"privateIpAddress"` |
473 | + DNSName string `xml:"privateDnsName"` |
474 | + IsPrimary bool `xml:"primary"` |
475 | +} |
476 | + |
477 | +// NetworkInterface describes a network interface for AWS VPC. |
478 | +// |
479 | +// See http://goo.gl/G63OQL for more details. |
480 | +type NetworkInterface struct { |
481 | + Id string `xml:"networkInterfaceId"` |
482 | + SubnetId string `xml:"subnetId"` |
483 | + VPCId string `xml:"vpcId"` |
484 | + AvailZone string `xml:"availabilityZone"` |
485 | + Description string `xml:"description"` |
486 | + OwnerId string `xml:"ownerId"` |
487 | + RequesterId string `xml:"requesterId"` |
488 | + RequesterManaged bool `xml:"requesterManaged"` |
489 | + Status string `xml:"status"` |
490 | + MACAddress string `xml:"macAddress"` |
491 | + PrivateIPAddress string `xml:"privateIpAddress"` |
492 | + PrivateDNSName string `xml:"privateDnsName"` |
493 | + SourceDestCheck bool `xml:"sourceDestCheck"` |
494 | + Groups []SecurityGroup `xml:"groupSet>item"` |
495 | + Attachment NetworkInterfaceAttachment `xml:"attachment"` |
496 | + Tags []Tag `xml:"tagSet>item"` |
497 | + PrivateIPs []PrivateIP `xml:"privateIpAddressesSet>item"` |
498 | +} |
499 | + |
500 | +// NetworkInterfaceOptions encapsulates options for the |
501 | +// CreateNetworkInterface call. |
502 | +// |
503 | +// Only the SubnetId is required, the rest are optional. |
504 | +// |
505 | +// One or more private IP addresses can be specified by using the |
506 | +// PrivateIPs slice. Only one of them can be set as primary. |
507 | +// |
508 | +// If PrivateIPs is empty, EC2 selects a primary private IP from the |
509 | +// subnet range. |
510 | +// |
511 | +// When SecondaryPrivateIPsCount is non-zero, EC2 allocates that |
512 | +// number of IP addresses from within the subnet range. The number of |
513 | +// IP addresses you can assign to a network interface varies by |
514 | +// instance type. |
515 | +type NetworkInterfaceOptions struct { |
516 | + SubnetId string |
517 | + PrivateIPs []PrivateIP |
518 | + SecondaryPrivateIPsCount int |
519 | + Description string |
520 | + SecurityGroupIds []string |
521 | +} |
522 | + |
523 | +// CreateNetworkInterfaceResp is the response to a |
524 | +// CreateNetworkInterface request. |
525 | +// |
526 | +// See http://goo.gl/ze3VhA for more details. |
527 | +type CreateNetworkInterfaceResp struct { |
528 | + RequestId string `xml:"requestId"` |
529 | + NetworkInterface NetworkInterface `xml:"networkInterface"` |
530 | +} |
531 | + |
532 | +// CreateNetworkInterface creates a network interface in the specified |
533 | +// subnet. |
534 | +// |
535 | +// See http://goo.gl/ze3VhA for more details. |
536 | +func (ec2 *EC2) CreateNetworkInterface(opts NetworkInterfaceOptions) (resp *CreateNetworkInterfaceResp, err error) { |
537 | + params := makeParamsVPC("CreateNetworkInterface") |
538 | + params["SubnetId"] = opts.SubnetId |
539 | + for i, ip := range opts.PrivateIPs { |
540 | + prefix := fmt.Sprintf("PrivateIpAddresses.%d.", i+1) |
541 | + params[prefix+"PrivateIpAddress"] = ip.Address |
542 | + params[prefix+"Primary"] = strconv.FormatBool(ip.IsPrimary) |
543 | + } |
544 | + if opts.Description != "" { |
545 | + params["Description"] = opts.Description |
546 | + } |
547 | + if opts.SecondaryPrivateIPsCount > 0 { |
548 | + count := strconv.Itoa(opts.SecondaryPrivateIPsCount) |
549 | + params["SecondaryPrivateIpAddressCount"] = count |
550 | + } |
551 | + for i, groupId := range opts.SecurityGroupIds { |
552 | + params["SecurityGroupId."+strconv.Itoa(i+1)] = groupId |
553 | + } |
554 | + resp = &CreateNetworkInterfaceResp{} |
555 | + err = ec2.query(params, resp) |
556 | + if err != nil { |
557 | + return nil, err |
558 | + } |
559 | + return resp, nil |
560 | +} |
561 | + |
562 | +// DeleteNetworkInterface deletes the specified network interface, |
563 | +// which must have been previously detached. |
564 | +// |
565 | +// See http://goo.gl/MC1yOj for more details. |
566 | +func (ec2 *EC2) DeleteNetworkInterface(id string) (resp *SimpleResp, err error) { |
567 | + params := makeParamsVPC("DeleteNetworkInterface") |
568 | + params["NetworkInterfaceId"] = id |
569 | + resp = &SimpleResp{} |
570 | + err = ec2.query(params, resp) |
571 | + if err != nil { |
572 | + return nil, err |
573 | + } |
574 | + return resp, nil |
575 | +} |
576 | + |
577 | +// NetworkInterfacesResp is the response to a NetworkInterfaces |
578 | +// request. |
579 | +// |
580 | +// See http://goo.gl/2LcXtM for more details. |
581 | +type NetworkInterfacesResp struct { |
582 | + RequestId string `xml:"requestId"` |
583 | + Interfaces []NetworkInterface `xml:"networkInterfaceSet>item"` |
584 | +} |
585 | + |
586 | +// NetworkInterfaces describes one or more network interfaces. Both |
587 | +// parameters are optional, and if specified will limit the returned |
588 | +// interfaces to the matching ids or filtering rules. |
589 | +// |
590 | +// See http://goo.gl/2LcXtM for more details. |
591 | +func (ec2 *EC2) NetworkInterfaces(ids []string, filter *Filter) (resp *NetworkInterfacesResp, err error) { |
592 | + params := makeParamsVPC("DescribeNetworkInterfaces") |
593 | + for i, id := range ids { |
594 | + params["NetworkInterfaceId."+strconv.Itoa(i+1)] = id |
595 | + } |
596 | + filter.addParams(params) |
597 | + |
598 | + resp = &NetworkInterfacesResp{} |
599 | + err = ec2.query(params, resp) |
600 | + if err != nil { |
601 | + return nil, err |
602 | + } |
603 | + return resp, nil |
604 | +} |
605 | + |
606 | +// AttachNetworkInterfaceResp is the response to an |
607 | +// AttachNetworkInterface request. |
608 | +// |
609 | +// See http://goo.gl/rEbSii for more details. |
610 | +type AttachNetworkInterfaceResp struct { |
611 | + RequestId string `xml:"requestId"` |
612 | + AttachmentId string `xml:"attachmentId"` |
613 | +} |
614 | + |
615 | +// AttachNetworkInterface attaches a network interface to an instance. |
616 | +// |
617 | +// See http://goo.gl/rEbSii for more details. |
618 | +func (ec2 *EC2) AttachNetworkInterface(interfaceId, instanceId string, deviceIndex int) (resp *AttachNetworkInterfaceResp, err error) { |
619 | + params := makeParamsVPC("AttachNetworkInterface") |
620 | + params["NetworkInterfaceId"] = interfaceId |
621 | + params["InstanceId"] = instanceId |
622 | + params["DeviceIndex"] = strconv.Itoa(deviceIndex) |
623 | + resp = &AttachNetworkInterfaceResp{} |
624 | + err = ec2.query(params, resp) |
625 | + if err != nil { |
626 | + return nil, err |
627 | + } |
628 | + return resp, nil |
629 | +} |
630 | + |
631 | +// DetachNetworkInterface detaches a network interface from an |
632 | +// instance. |
633 | +// |
634 | +// See http://goo.gl/0Xc1px for more details. |
635 | +func (ec2 *EC2) DetachNetworkInterface(attachmentId string, force bool) (resp *SimpleResp, err error) { |
636 | + params := makeParamsVPC("DetachNetworkInterface") |
637 | + params["AttachmentId"] = attachmentId |
638 | + if force { |
639 | + // Force is optional. |
640 | + params["Force"] = "true" |
641 | + } |
642 | + resp = &SimpleResp{} |
643 | + err = ec2.query(params, resp) |
644 | + if err != nil { |
645 | + return nil, err |
646 | + } |
647 | + return resp, nil |
648 | +} |
649 | |
650 | === added file 'ec2/networkinterfaces_test.go' |
651 | --- ec2/networkinterfaces_test.go 1970-01-01 00:00:00 +0000 |
652 | +++ ec2/networkinterfaces_test.go 2014-02-07 12:56:14 +0000 |
653 | @@ -0,0 +1,367 @@ |
654 | +// |
655 | +// goamz - Go packages to interact with the Amazon Web Services. |
656 | +// |
657 | +// https://wiki.ubuntu.com/goamz |
658 | +// |
659 | +// Copyright (c) 2014 Canonical Ltd. |
660 | +// |
661 | +// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com> |
662 | +// |
663 | + |
664 | +package ec2_test |
665 | + |
666 | +import ( |
667 | + "launchpad.net/goamz/aws" |
668 | + "launchpad.net/goamz/ec2" |
669 | + . "launchpad.net/gocheck" |
670 | + "time" |
671 | +) |
672 | + |
673 | +// Network interface tests with example responses |
674 | + |
675 | +func (s *S) TestCreateNetworkInterfaceExample(c *C) { |
676 | + testServer.Response(200, nil, CreateNetworkInterfaceExample) |
677 | + |
678 | + resp, err := s.ec2.CreateNetworkInterface(ec2.NetworkInterfaceOptions{ |
679 | + SubnetId: "subnet-b2a249da", |
680 | + PrivateIPs: []ec2.PrivateIP{ |
681 | + {Address: "10.0.2.157", IsPrimary: true}, |
682 | + }, |
683 | + SecurityGroupIds: []string{"sg-1a2b3c4d"}, |
684 | + }) |
685 | + req := testServer.WaitRequest() |
686 | + |
687 | + c.Assert(req.Form["Action"], DeepEquals, []string{"CreateNetworkInterface"}) |
688 | + c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-b2a249da"}) |
689 | + c.Assert(req.Form["PrivateIpAddress"], HasLen, 0) |
690 | + c.Assert( |
691 | + req.Form["PrivateIpAddresses.1.PrivateIpAddress"], |
692 | + DeepEquals, |
693 | + []string{"10.0.2.157"}, |
694 | + ) |
695 | + c.Assert( |
696 | + req.Form["PrivateIpAddresses.1.Primary"], |
697 | + DeepEquals, |
698 | + []string{"true"}, |
699 | + ) |
700 | + c.Assert(req.Form["Description"], HasLen, 0) |
701 | + c.Assert(req.Form["SecurityGroupId.1"], DeepEquals, []string{"sg-1a2b3c4d"}) |
702 | + |
703 | + c.Assert(err, IsNil) |
704 | + c.Assert(resp.RequestId, Equals, "8dbe591e-5a22-48cb-b948-dd0aadd55adf") |
705 | + iface := resp.NetworkInterface |
706 | + c.Check(iface.Id, Equals, "eni-cfca76a6") |
707 | + c.Check(iface.SubnetId, Equals, "subnet-b2a249da") |
708 | + c.Check(iface.VPCId, Equals, "vpc-c31dafaa") |
709 | + c.Check(iface.AvailZone, Equals, "ap-southeast-1b") |
710 | + c.Check(iface.Description, Equals, "") |
711 | + c.Check(iface.OwnerId, Equals, "251839141158") |
712 | + c.Check(iface.RequesterManaged, Equals, false) |
713 | + c.Check(iface.Status, Equals, ec2.AvailableStatus) |
714 | + c.Check(iface.MACAddress, Equals, "02:74:b0:72:79:61") |
715 | + c.Check(iface.PrivateIPAddress, Equals, "10.0.2.157") |
716 | + c.Check(iface.SourceDestCheck, Equals, true) |
717 | + c.Check(iface.Groups, DeepEquals, []ec2.SecurityGroup{ |
718 | + {Id: "sg-1a2b3c4d", Name: "default"}, |
719 | + }) |
720 | + c.Check(iface.Tags, HasLen, 0) |
721 | + c.Check(iface.PrivateIPs, DeepEquals, []ec2.PrivateIP{ |
722 | + {Address: "10.0.2.157", IsPrimary: true}, |
723 | + }) |
724 | +} |
725 | + |
726 | +func (s *S) TestDeleteNetworkInterfaceExample(c *C) { |
727 | + testServer.Response(200, nil, DeleteNetworkInterfaceExample) |
728 | + |
729 | + resp, err := s.ec2.DeleteNetworkInterface("eni-id") |
730 | + req := testServer.WaitRequest() |
731 | + |
732 | + c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteNetworkInterface"}) |
733 | + c.Assert(req.Form["NetworkInterfaceId"], DeepEquals, []string{"eni-id"}) |
734 | + |
735 | + c.Assert(err, IsNil) |
736 | + c.Assert(resp.RequestId, Equals, "e1c6d73b-edaa-4e62-9909-6611404e1739") |
737 | +} |
738 | + |
739 | +func (s *S) TestNetworkInterfacesExample(c *C) { |
740 | + testServer.Response(200, nil, DescribeNetworkInterfacesExample) |
741 | + |
742 | + ids := []string{"eni-0f62d866", "eni-a66ed5cf"} |
743 | + resp, err := s.ec2.NetworkInterfaces(ids, nil) |
744 | + req := testServer.WaitRequest() |
745 | + |
746 | + c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeNetworkInterfaces"}) |
747 | + c.Assert(req.Form["NetworkInterfaceId.1"], DeepEquals, []string{ids[0]}) |
748 | + c.Assert(req.Form["NetworkInterfaceId.2"], DeepEquals, []string{ids[1]}) |
749 | + |
750 | + c.Assert(err, IsNil) |
751 | + c.Assert(resp.RequestId, Equals, "fc45294c-006b-457b-bab9-012f5b3b0e40") |
752 | + c.Check(resp.Interfaces, HasLen, 2) |
753 | + iface := resp.Interfaces[0] |
754 | + c.Check(iface.Id, Equals, ids[0]) |
755 | + c.Check(iface.SubnetId, Equals, "subnet-c53c87ac") |
756 | + c.Check(iface.VPCId, Equals, "vpc-cc3c87a5") |
757 | + c.Check(iface.AvailZone, Equals, "ap-southeast-1b") |
758 | + c.Check(iface.Description, Equals, "") |
759 | + c.Check(iface.OwnerId, Equals, "053230519467") |
760 | + c.Check(iface.RequesterManaged, Equals, false) |
761 | + c.Check(iface.Status, Equals, ec2.InUseStatus) |
762 | + c.Check(iface.MACAddress, Equals, "02:81:60:cb:27:37") |
763 | + c.Check(iface.PrivateIPAddress, Equals, "10.0.0.146") |
764 | + c.Check(iface.SourceDestCheck, Equals, true) |
765 | + c.Check(iface.Groups, DeepEquals, []ec2.SecurityGroup{ |
766 | + {Id: "sg-3f4b5653", Name: "default"}, |
767 | + }) |
768 | + c.Check(iface.Attachment, DeepEquals, ec2.NetworkInterfaceAttachment{ |
769 | + Id: "eni-attach-6537fc0c", |
770 | + InstanceId: "i-22197876", |
771 | + InstanceOwnerId: "053230519467", |
772 | + DeviceIndex: 0, |
773 | + Status: ec2.AttachedStatus, |
774 | + AttachTime: "2012-07-01T21:45:27.000Z", |
775 | + DeleteOnTermination: true, |
776 | + }) |
777 | + c.Check(iface.PrivateIPs, DeepEquals, []ec2.PrivateIP{ |
778 | + {Address: "10.0.0.146", IsPrimary: true}, |
779 | + {Address: "10.0.0.148", IsPrimary: false}, |
780 | + {Address: "10.0.0.150", IsPrimary: false}, |
781 | + }) |
782 | + c.Check(iface.Tags, HasLen, 0) |
783 | + |
784 | + iface = resp.Interfaces[1] |
785 | + c.Check(iface.Id, Equals, ids[1]) |
786 | + c.Check(iface.SubnetId, Equals, "subnet-cd8a35a4") |
787 | + c.Check(iface.VPCId, Equals, "vpc-f28a359b") |
788 | + c.Check(iface.AvailZone, Equals, "ap-southeast-1b") |
789 | + c.Check(iface.Description, Equals, "Primary network interface") |
790 | + c.Check(iface.OwnerId, Equals, "053230519467") |
791 | + c.Check(iface.RequesterManaged, Equals, false) |
792 | + c.Check(iface.Status, Equals, "in-use") |
793 | + c.Check(iface.MACAddress, Equals, "02:78:d7:00:8a:1e") |
794 | + c.Check(iface.PrivateIPAddress, Equals, "10.0.1.233") |
795 | + c.Check(iface.SourceDestCheck, Equals, true) |
796 | + c.Check(iface.Groups, DeepEquals, []ec2.SecurityGroup{ |
797 | + {Id: "sg-a2a0b2ce", Name: "quick-start-1"}, |
798 | + }) |
799 | + c.Check(iface.Attachment, DeepEquals, ec2.NetworkInterfaceAttachment{ |
800 | + Id: "eni-attach-a99c57c0", |
801 | + InstanceId: "i-886401dc", |
802 | + InstanceOwnerId: "053230519467", |
803 | + DeviceIndex: 0, |
804 | + Status: ec2.AttachedStatus, |
805 | + AttachTime: "2012-06-27T20:08:44.000Z", |
806 | + DeleteOnTermination: true, |
807 | + }) |
808 | + c.Check(iface.PrivateIPs, DeepEquals, []ec2.PrivateIP{ |
809 | + {Address: "10.0.1.233", IsPrimary: true}, |
810 | + {Address: "10.0.1.20", IsPrimary: false}, |
811 | + }) |
812 | + c.Check(iface.Tags, HasLen, 0) |
813 | +} |
814 | + |
815 | +func (s *S) TestAttachNetworkInterfaceExample(c *C) { |
816 | + testServer.Response(200, nil, AttachNetworkInterfaceExample) |
817 | + |
818 | + resp, err := s.ec2.AttachNetworkInterface("eni-id", "i-id", 0) |
819 | + req := testServer.WaitRequest() |
820 | + |
821 | + c.Assert(req.Form["Action"], DeepEquals, []string{"AttachNetworkInterface"}) |
822 | + c.Assert(req.Form["NetworkInterfaceId"], DeepEquals, []string{"eni-id"}) |
823 | + c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-id"}) |
824 | + c.Assert(req.Form["DeviceIndex"], DeepEquals, []string{"0"}) |
825 | + |
826 | + c.Assert(err, IsNil) |
827 | + c.Assert(resp.RequestId, Equals, "ace8cd1e-e685-4e44-90fb-92014d907212") |
828 | + c.Assert(resp.AttachmentId, Equals, "eni-attach-d94b09b0") |
829 | +} |
830 | + |
831 | +func (s *S) TestDetachNetworkInterfaceExample(c *C) { |
832 | + testServer.Response(200, nil, DetachNetworkInterfaceExample) |
833 | + |
834 | + resp, err := s.ec2.DetachNetworkInterface("eni-attach-id", true) |
835 | + req := testServer.WaitRequest() |
836 | + |
837 | + c.Assert(req.Form["Action"], DeepEquals, []string{"DetachNetworkInterface"}) |
838 | + c.Assert(req.Form["AttachmentId"], DeepEquals, []string{"eni-attach-id"}) |
839 | + c.Assert(req.Form["Force"], DeepEquals, []string{"true"}) |
840 | + |
841 | + c.Assert(err, IsNil) |
842 | + c.Assert(resp.RequestId, Equals, "ce540707-0635-46bc-97da-33a8a362a0e8") |
843 | +} |
844 | + |
845 | +// Network interface tests run against either a local test server or |
846 | +// live on EC2. |
847 | + |
848 | +func (s *ServerTests) TestNetworkInterfaces(c *C) { |
849 | + vpcResp, err := s.ec2.CreateVPC("10.3.0.0/16", "") |
850 | + c.Assert(err, IsNil) |
851 | + vpcId := vpcResp.VPC.Id |
852 | + defer s.deleteVPCs(c, []string{vpcId}) |
853 | + |
854 | + subResp := s.createSubnet(c, vpcId, "10.3.1.0/24", "") |
855 | + subId := subResp.Subnet.Id |
856 | + defer s.deleteSubnets(c, []string{subId}) |
857 | + |
858 | + sg := s.makeTestGroupVPC(c, vpcId, "vpc-sg-1", "vpc test group1") |
859 | + defer s.deleteGroups(c, []ec2.SecurityGroup{sg}) |
860 | + |
861 | + instList, err := s.ec2.RunInstances(&ec2.RunInstances{ |
862 | + ImageId: imageId, |
863 | + InstanceType: "t1.micro", |
864 | + SubnetId: subId, |
865 | + }) |
866 | + c.Assert(err, IsNil) |
867 | + inst := instList.Instances[0] |
868 | + c.Assert(inst, NotNil) |
869 | + instId := inst.InstanceId |
870 | + defer s.ec2.TerminateInstances([]string{instId}) |
871 | + |
872 | + ips1 := []ec2.PrivateIP{{Address: "10.3.1.10", IsPrimary: true}} |
873 | + resp1, err := s.ec2.CreateNetworkInterface(ec2.NetworkInterfaceOptions{ |
874 | + SubnetId: subId, |
875 | + PrivateIPs: ips1, |
876 | + Description: "My first iface", |
877 | + }) |
878 | + c.Assert(err, IsNil) |
879 | + assertNetworkInterface(c, resp1.NetworkInterface, "", subId, ips1) |
880 | + c.Check(resp1.NetworkInterface.Description, Equals, "My first iface") |
881 | + id1 := resp1.NetworkInterface.Id |
882 | + |
883 | + ips2 := []ec2.PrivateIP{ |
884 | + {Address: "10.3.1.20", IsPrimary: true}, |
885 | + {Address: "10.3.1.22", IsPrimary: false}, |
886 | + } |
887 | + resp2, err := s.ec2.CreateNetworkInterface(ec2.NetworkInterfaceOptions{ |
888 | + SubnetId: subId, |
889 | + PrivateIPs: ips2, |
890 | + SecurityGroupIds: []string{sg.Id}, |
891 | + }) |
892 | + c.Assert(err, IsNil) |
893 | + assertNetworkInterface(c, resp2.NetworkInterface, "", subId, ips2) |
894 | + c.Assert(resp2.NetworkInterface.Groups, DeepEquals, []ec2.SecurityGroup{sg}) |
895 | + id2 := resp2.NetworkInterface.Id |
896 | + |
897 | + // We only check for the network interfaces we just created, |
898 | + // because the user might have others in his account (when testing |
899 | + // against the EC2 servers). In some cases it takes a short while |
900 | + // until both interfaces are created, so we need to retry a few |
901 | + // times to make sure. |
902 | + testAttempt := aws.AttemptStrategy{ |
903 | + Total: 5 * time.Minute, |
904 | + Delay: 5 * time.Second, |
905 | + } |
906 | + var list *ec2.NetworkInterfacesResp |
907 | + done := false |
908 | + for a := testAttempt.Start(); a.Next(); { |
909 | + c.Logf("waiting for %v to be created", []string{id1, id2}) |
910 | + list, err = s.ec2.NetworkInterfaces(nil, nil) |
911 | + if err != nil { |
912 | + c.Logf("retrying; NetworkInterfaces returned: %v", err) |
913 | + continue |
914 | + } |
915 | + found := 0 |
916 | + for _, iface := range list.Interfaces { |
917 | + c.Logf("found NIC %v", iface) |
918 | + switch iface.Id { |
919 | + case id1: |
920 | + assertNetworkInterface(c, iface, id1, subId, ips1) |
921 | + found++ |
922 | + case id2: |
923 | + assertNetworkInterface(c, iface, id2, subId, ips2) |
924 | + found++ |
925 | + } |
926 | + if found == 2 { |
927 | + done = true |
928 | + break |
929 | + } |
930 | + } |
931 | + if done { |
932 | + c.Logf("all NICs were created") |
933 | + break |
934 | + } |
935 | + } |
936 | + if !done { |
937 | + c.Fatalf("timeout while waiting for NICs %v", []string{id1, id2}) |
938 | + } |
939 | + |
940 | + list, err = s.ec2.NetworkInterfaces([]string{id1}, nil) |
941 | + c.Assert(err, IsNil) |
942 | + c.Assert(list.Interfaces, HasLen, 1) |
943 | + assertNetworkInterface(c, list.Interfaces[0], id1, subId, ips1) |
944 | + |
945 | + f := ec2.NewFilter() |
946 | + f.Add("network-interface-id", id2) |
947 | + list, err = s.ec2.NetworkInterfaces(nil, f) |
948 | + c.Assert(err, IsNil) |
949 | + c.Assert(list.Interfaces, HasLen, 1) |
950 | + assertNetworkInterface(c, list.Interfaces[0], id2, subId, ips2) |
951 | + |
952 | + // Attachment might fail if the instance is not running yet, |
953 | + // so we retry for a while until it succeeds. |
954 | + var attResp *ec2.AttachNetworkInterfaceResp |
955 | + for a := testAttempt.Start(); a.Next(); { |
956 | + attResp, err = s.ec2.AttachNetworkInterface(id2, instId, 1) |
957 | + if err != nil { |
958 | + c.Logf("AttachNetworkInterface returned: %v; retrying...", err) |
959 | + attResp = nil |
960 | + continue |
961 | + } |
962 | + c.Logf("AttachNetworkInterface succeeded") |
963 | + c.Check(attResp.AttachmentId, Not(Equals), "") |
964 | + break |
965 | + } |
966 | + if attResp == nil { |
967 | + c.Fatalf("timeout while waiting for AttachNetworkInterface to succeed") |
968 | + } |
969 | + |
970 | + list, err = s.ec2.NetworkInterfaces([]string{id2}, nil) |
971 | + c.Assert(err, IsNil) |
972 | + att := list.Interfaces[0].Attachment |
973 | + c.Check(att.Id, Equals, attResp.AttachmentId) |
974 | + c.Check(att.InstanceId, Equals, instId) |
975 | + c.Check(att.DeviceIndex, Equals, 1) |
976 | + c.Check(att.Status, Matches, "("+ec2.AttachingStatus+"|"+ec2.InUseStatus+")") |
977 | + |
978 | + _, err = s.ec2.DetachNetworkInterface(att.Id, true) |
979 | + c.Check(err, IsNil) |
980 | + |
981 | + _, err = s.ec2.DeleteNetworkInterface(id1) |
982 | + c.Assert(err, IsNil) |
983 | + |
984 | + // We might not be able to delete the interface until the |
985 | + // detachment is completed, so we need to retry here as well. |
986 | + for a := testAttempt.Start(); a.Next(); { |
987 | + _, err = s.ec2.DeleteNetworkInterface(id2) |
988 | + if err != nil { |
989 | + c.Logf("DeleteNetworkInterface returned: %v; retrying...", err) |
990 | + continue |
991 | + } |
992 | + c.Logf("DeleteNetworkInterface succeeded") |
993 | + return |
994 | + } |
995 | + c.Fatalf("timeout while waiting for DeleteNetworkInterface to succeed") |
996 | +} |
997 | + |
998 | +func assertNetworkInterface(c *C, obtained ec2.NetworkInterface, expectId, expectSubId string, expectIPs []ec2.PrivateIP) { |
999 | + if expectId != "" { |
1000 | + c.Check(obtained.Id, Equals, expectId) |
1001 | + } else { |
1002 | + c.Check(obtained.Id, Matches, `^eni-[0-9a-f]+$`) |
1003 | + } |
1004 | + statusRegExp := "(" + |
1005 | + ec2.AvailableStatus + "|" + |
1006 | + ec2.PendingStatus + "|" + |
1007 | + ec2.InUseStatus + |
1008 | + ")" |
1009 | + c.Check(obtained.Status, Matches, statusRegExp) |
1010 | + if expectSubId != "" { |
1011 | + c.Check(obtained.SubnetId, Equals, expectSubId) |
1012 | + } else { |
1013 | + c.Check(obtained.SubnetId, Matches, `^subnet-[0-9a-f]+$`) |
1014 | + } |
1015 | + c.Check(obtained.Attachment, DeepEquals, ec2.NetworkInterfaceAttachment{}) |
1016 | + if len(expectIPs) > 0 { |
1017 | + c.Check(obtained.PrivateIPs, DeepEquals, expectIPs) |
1018 | + c.Check(obtained.PrivateIPAddress, DeepEquals, expectIPs[0].Address) |
1019 | + } |
1020 | +} |
1021 | |
1022 | === modified file 'ec2/responses_test.go' |
1023 | --- ec2/responses_test.go 2014-02-07 12:56:14 +0000 |
1024 | +++ ec2/responses_test.go 2014-02-07 12:56:14 +0000 |
1025 | @@ -684,3 +684,151 @@ |
1026 | </subnetSet> |
1027 | </DescribeSubnetsResponse> |
1028 | ` |
1029 | + |
1030 | +// http://goo.gl/ze3VhA |
1031 | +var CreateNetworkInterfaceExample = ` |
1032 | +<CreateNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> |
1033 | + <requestId>8dbe591e-5a22-48cb-b948-dd0aadd55adf</requestId> |
1034 | + <networkInterface> |
1035 | + <networkInterfaceId>eni-cfca76a6</networkInterfaceId> |
1036 | + <subnetId>subnet-b2a249da</subnetId> |
1037 | + <vpcId>vpc-c31dafaa</vpcId> |
1038 | + <availabilityZone>ap-southeast-1b</availabilityZone> |
1039 | + <description/> |
1040 | + <ownerId>251839141158</ownerId> |
1041 | + <requesterManaged>false</requesterManaged> |
1042 | + <status>available</status> |
1043 | + <macAddress>02:74:b0:72:79:61</macAddress> |
1044 | + <privateIpAddress>10.0.2.157</privateIpAddress> |
1045 | + <sourceDestCheck>true</sourceDestCheck> |
1046 | + <groupSet> |
1047 | + <item> |
1048 | + <groupId>sg-1a2b3c4d</groupId> |
1049 | + <groupName>default</groupName> |
1050 | + </item> |
1051 | + </groupSet> |
1052 | + <tagSet/> |
1053 | + <privateIpAddressesSet> |
1054 | + <item> |
1055 | + <privateIpAddress>10.0.2.157</privateIpAddress> |
1056 | + <primary>true</primary> |
1057 | + </item> |
1058 | + </privateIpAddressesSet> |
1059 | + </networkInterface> |
1060 | +</CreateNetworkInterfaceResponse> |
1061 | +` |
1062 | + |
1063 | +// http://goo.gl/MC1yOj |
1064 | +var DeleteNetworkInterfaceExample = ` |
1065 | +<DeleteNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> |
1066 | + <requestId>e1c6d73b-edaa-4e62-9909-6611404e1739</requestId> |
1067 | + <return>true</return> |
1068 | +</DeleteNetworkInterfaceResponse> |
1069 | +` |
1070 | + |
1071 | +// http://goo.gl/2LcXtM |
1072 | +var DescribeNetworkInterfacesExample = ` |
1073 | +<DescribeNetworkInterfacesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> |
1074 | + <requestId>fc45294c-006b-457b-bab9-012f5b3b0e40</requestId> |
1075 | + <networkInterfaceSet> |
1076 | + <item> |
1077 | + <networkInterfaceId>eni-0f62d866</networkInterfaceId> |
1078 | + <subnetId>subnet-c53c87ac</subnetId> |
1079 | + <vpcId>vpc-cc3c87a5</vpcId> |
1080 | + <availabilityZone>ap-southeast-1b</availabilityZone> |
1081 | + <description/> |
1082 | + <ownerId>053230519467</ownerId> |
1083 | + <requesterManaged>false</requesterManaged> |
1084 | + <status>in-use</status> |
1085 | + <macAddress>02:81:60:cb:27:37</macAddress> |
1086 | + <privateIpAddress>10.0.0.146</privateIpAddress> |
1087 | + <sourceDestCheck>true</sourceDestCheck> |
1088 | + <groupSet> |
1089 | + <item> |
1090 | + <groupId>sg-3f4b5653</groupId> |
1091 | + <groupName>default</groupName> |
1092 | + </item> |
1093 | + </groupSet> |
1094 | + <attachment> |
1095 | + <attachmentId>eni-attach-6537fc0c</attachmentId> |
1096 | + <instanceId>i-22197876</instanceId> |
1097 | + <instanceOwnerId>053230519467</instanceOwnerId> |
1098 | + <deviceIndex>0</deviceIndex> |
1099 | + <status>attached</status> |
1100 | + <attachTime>2012-07-01T21:45:27.000Z</attachTime> |
1101 | + <deleteOnTermination>true</deleteOnTermination> |
1102 | + </attachment> |
1103 | + <tagSet/> |
1104 | + <privateIpAddressesSet> |
1105 | + <item> |
1106 | + <privateIpAddress>10.0.0.146</privateIpAddress> |
1107 | + <primary>true</primary> |
1108 | + </item> |
1109 | + <item> |
1110 | + <privateIpAddress>10.0.0.148</privateIpAddress> |
1111 | + <primary>false</primary> |
1112 | + </item> |
1113 | + <item> |
1114 | + <privateIpAddress>10.0.0.150</privateIpAddress> |
1115 | + <primary>false</primary> |
1116 | + </item> |
1117 | + </privateIpAddressesSet> |
1118 | + </item> |
1119 | + <item> |
1120 | + <networkInterfaceId>eni-a66ed5cf</networkInterfaceId> |
1121 | + <subnetId>subnet-cd8a35a4</subnetId> |
1122 | + <vpcId>vpc-f28a359b</vpcId> |
1123 | + <availabilityZone>ap-southeast-1b</availabilityZone> |
1124 | + <description>Primary network interface</description> |
1125 | + <ownerId>053230519467</ownerId> |
1126 | + <requesterManaged>false</requesterManaged> |
1127 | + <status>in-use</status> |
1128 | + <macAddress>02:78:d7:00:8a:1e</macAddress> |
1129 | + <privateIpAddress>10.0.1.233</privateIpAddress> |
1130 | + <sourceDestCheck>true</sourceDestCheck> |
1131 | + <groupSet> |
1132 | + <item> |
1133 | + <groupId>sg-a2a0b2ce</groupId> |
1134 | + <groupName>quick-start-1</groupName> |
1135 | + </item> |
1136 | + </groupSet> |
1137 | + <attachment> |
1138 | + <attachmentId>eni-attach-a99c57c0</attachmentId> |
1139 | + <instanceId>i-886401dc</instanceId> |
1140 | + <instanceOwnerId>053230519467</instanceOwnerId> |
1141 | + <deviceIndex>0</deviceIndex> |
1142 | + <status>attached</status> |
1143 | + <attachTime>2012-06-27T20:08:44.000Z</attachTime> |
1144 | + <deleteOnTermination>true</deleteOnTermination> |
1145 | + </attachment> |
1146 | + <tagSet/> |
1147 | + <privateIpAddressesSet> |
1148 | + <item> |
1149 | + <privateIpAddress>10.0.1.233</privateIpAddress> |
1150 | + <primary>true</primary> |
1151 | + </item> |
1152 | + <item> |
1153 | + <privateIpAddress>10.0.1.20</privateIpAddress> |
1154 | + <primary>false</primary> |
1155 | + </item> |
1156 | + </privateIpAddressesSet> |
1157 | + </item> |
1158 | + </networkInterfaceSet> |
1159 | +</DescribeNetworkInterfacesResponse> |
1160 | +` |
1161 | + |
1162 | +// http://goo.gl/rEbSii |
1163 | +var AttachNetworkInterfaceExample = ` |
1164 | +<AttachNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> |
1165 | + <requestId>ace8cd1e-e685-4e44-90fb-92014d907212</requestId> |
1166 | + <attachmentId>eni-attach-d94b09b0</attachmentId> |
1167 | +</AttachNetworkInterfaceResponse> |
1168 | +` |
1169 | + |
1170 | +// http://goo.gl/0Xc1px |
1171 | +var DetachNetworkInterfaceExample = ` |
1172 | +<DetachNetworkInterfaceResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> |
1173 | + <requestId>ce540707-0635-46bc-97da-33a8a362a0e8</requestId> |
1174 | + <return>true</return> |
1175 | +</DetachNetworkInterfaceResponse> |
1176 | +` |
1177 | |
1178 | === modified file 'ec2/subnets_test.go' |
1179 | --- ec2/subnets_test.go 2014-02-07 12:56:14 +0000 |
1180 | +++ ec2/subnets_test.go 2014-02-07 12:56:14 +0000 |
1181 | @@ -91,7 +91,7 @@ |
1182 | c.Check(subnet.Tags, HasLen, 0) |
1183 | } |
1184 | |
1185 | -// Subnet tests run against either a local test server or live on EC2. |
1186 | +// Subnet tests to run against either a local test server or live on EC2. |
1187 | |
1188 | func (s *ServerTests) TestSubnets(c *C) { |
1189 | resp, err := s.ec2.CreateVPC("10.2.0.0/16", "") |
1190 | @@ -99,30 +99,7 @@ |
1191 | vpcId := resp.VPC.Id |
1192 | defer s.deleteVPCs(c, []string{vpcId}) |
1193 | |
1194 | - // It might take some time for the VPC to get created, so we need |
1195 | - // to retry a few times when running against the EC2 servers. |
1196 | - var resp1 *ec2.CreateSubnetResp |
1197 | - testAttempt := aws.AttemptStrategy{ |
1198 | - Total: 2 * time.Minute, |
1199 | - Delay: 5 * time.Second, |
1200 | - } |
1201 | - done := false |
1202 | - for a := testAttempt.Start(); a.Next(); { |
1203 | - resp1, err = s.ec2.CreateSubnet(vpcId, "10.2.1.0/24", "") |
1204 | - if s.errorCode(err) == "InvalidVpcID.NotFound" { |
1205 | - c.Logf("VPC %v not created yet; retrying", vpcId) |
1206 | - continue |
1207 | - } |
1208 | - if err != nil { |
1209 | - c.Logf("retrying; CreateSubnet returned: %v", err) |
1210 | - continue |
1211 | - } |
1212 | - done = true |
1213 | - break |
1214 | - } |
1215 | - if !done { |
1216 | - c.Fatalf("timeout while waiting for VPC and subnet") |
1217 | - } |
1218 | + resp1 := s.createSubnet(c, vpcId, "10.2.1.0/24", "") |
1219 | assertSubnet(c, resp1.Subnet, "", vpcId, "10.2.1.0/24") |
1220 | id1 := resp1.Subnet.Id |
1221 | |
1222 | @@ -136,8 +113,12 @@ |
1223 | // servers). In some cases it takes a short while until both |
1224 | // subnets are created, so we need to retry a few times to make |
1225 | // sure. |
1226 | + testAttempt := aws.AttemptStrategy{ |
1227 | + Total: 2 * time.Minute, |
1228 | + Delay: 5 * time.Second, |
1229 | + } |
1230 | var list *ec2.SubnetsResp |
1231 | - done = false |
1232 | + done := false |
1233 | for a := testAttempt.Start(); a.Next(); { |
1234 | c.Logf("waiting for %v to be created", []string{id1, id2}) |
1235 | list, err = s.ec2.Subnets(nil, nil) |
1236 | @@ -188,6 +169,62 @@ |
1237 | c.Assert(err, IsNil) |
1238 | } |
1239 | |
1240 | +// createSubnet ensures a subnet with the given vpcId and cidrBlock |
1241 | +// gets created, retrying a few times with a timeout. This needs to be |
1242 | +// done when testing against EC2 servers, because if the VPC was just |
1243 | +// created it might take some time for it to show up, so the subnet |
1244 | +// can be created. |
1245 | +func (s *ServerTests) createSubnet(c *C, vpcId, cidrBlock, availZone string) *ec2.CreateSubnetResp { |
1246 | + testAttempt := aws.AttemptStrategy{ |
1247 | + Total: 2 * time.Minute, |
1248 | + Delay: 5 * time.Second, |
1249 | + } |
1250 | + for a := testAttempt.Start(); a.Next(); { |
1251 | + resp, err := s.ec2.CreateSubnet(vpcId, cidrBlock, availZone) |
1252 | + if s.errorCode(err) == "InvalidVpcID.NotFound" { |
1253 | + c.Logf("VPC %v not created yet; retrying", vpcId) |
1254 | + continue |
1255 | + } |
1256 | + if err != nil { |
1257 | + c.Logf("retrying; CreateSubnet returned: %v", err) |
1258 | + continue |
1259 | + } |
1260 | + return resp |
1261 | + } |
1262 | + c.Fatalf("timeout while waiting for VPC and subnet") |
1263 | + return nil |
1264 | +} |
1265 | + |
1266 | +// deleteSubnets ensures the given subnets are deleted, by retrying |
1267 | +// until a timeout or all subnets cannot be found anymore. This |
1268 | +// should be used to make sure tests leave no subnets around. |
1269 | +func (s *ServerTests) deleteSubnets(c *C, ids []string) { |
1270 | + testAttempt := aws.AttemptStrategy{ |
1271 | + Total: 2 * time.Minute, |
1272 | + Delay: 5 * time.Second, |
1273 | + } |
1274 | + for a := testAttempt.Start(); a.Next(); { |
1275 | + deleted := 0 |
1276 | + c.Logf("deleting subnets %v", ids) |
1277 | + for _, id := range ids { |
1278 | + _, err := s.ec2.DeleteSubnet(id) |
1279 | + if err == nil || s.errorCode(err) == "InvalidSubnetID.NotFound" { |
1280 | + c.Logf("subnet %s deleted", id) |
1281 | + deleted++ |
1282 | + continue |
1283 | + } |
1284 | + if err != nil { |
1285 | + c.Logf("retrying; DeleteSubnet returned: %v", err) |
1286 | + } |
1287 | + } |
1288 | + if deleted == len(ids) { |
1289 | + c.Logf("all subnets deleted") |
1290 | + return |
1291 | + } |
1292 | + } |
1293 | + c.Fatalf("timeout while waiting %v subnets to get deleted!", ids) |
1294 | +} |
1295 | + |
1296 | func assertSubnet(c *C, obtained ec2.Subnet, expectId, expectVpcId, expectCidr string) { |
1297 | if expectId != "" { |
1298 | c.Check(obtained.Id, Equals, expectId) |
Reviewers: mp+204912_ code.launchpad. net,
Message:
Please take a look.
Description: e-related APIs
ec2: Add VPC NetworkInterfac
Added the following new API calls: terface terface terface terface
- CreateNetworkIn
- DeleteNetworkIn
- NetworkInterfaces
- AttachNetworkIn
- DetachNetworkIn
(and related types/responses)
Modified existing calls/types: roupVPC call that does roup, but sets
- SecurityGroupInfo now includes VPCId field
- Add CreateSecurityG
the same as CreateSecurityG
the VPCId, to create a VPC group
This enables us to handle VPC NICs with
goamz and partially handle private IP
addresses for them. Next, we'll add more
calls for private IP addresses management.
Tested live on EC2, extended ec2test package
as needed.
https:/ /code.launchpad .net/~dimitern/ goamz/nic- api-calls/ +merge/ 204912
Requires: /code.launchpad .net/~dimitern/ goamz/subnets- api-calls/ +merge/ 204640
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/54570048/
Affected files (+1004, -15 lines): server. go rfaces. go rfaces_ test.go test.go
A [revision details]
M ec2/ec2.go
M ec2/ec2_test.go
M ec2/ec2t_test.go
M ec2/ec2test/
A ec2/networkinte
A ec2/networkinte
M ec2/responses_