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

Proposed by Dimiter Naydenov
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
Reviewer Review Type Date Requested Status
Dimiter Naydenov (community) Approve
Review via email: mp+204912@code.launchpad.net

Description of the change

ec2: Add VPC NetworkInterface-related APIs

Added the following new API calls:
- CreateNetworkInterface
- DeleteNetworkInterface
- NetworkInterfaces
- AttachNetworkInterface
- DetachNetworkInterface
(and related types/responses)

Modified existing calls/types:
- SecurityGroupInfo now includes VPCId field
- Add CreateSecurityGroupVPC call that does
the same as CreateSecurityGroup, but sets
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.

https://codereview.appspot.com/54570048/

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

Reviewers: mp+204912_code.launchpad.net,

Message:
Please take a look.

Description:
ec2: Add VPC NetworkInterface-related APIs

Added the following new API calls:
- CreateNetworkInterface
- DeleteNetworkInterface
- NetworkInterfaces
- AttachNetworkInterface
- DetachNetworkInterface
(and related types/responses)

Modified existing calls/types:
- SecurityGroupInfo now includes VPCId field
- Add CreateSecurityGroupVPC call that does
the same as CreateSecurityGroup, but sets
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:
https://code.launchpad.net/~dimitern/goamz/subnets-api-calls/+merge/204640

(do not edit description out of merge proposal)

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

Affected files (+1004, -15 lines):
   A [revision details]
   M ec2/ec2.go
   M ec2/ec2_test.go
   M ec2/ec2t_test.go
   M ec2/ec2test/server.go
   A ec2/networkinterfaces.go
   A ec2/networkinterfaces_test.go
   M ec2/responses_test.go

lp:~dimitern/goamz/nic-api-calls updated
54. By Dimiter Naydenov

Fixed a test slightly

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

LGTM with some minor suggestions below.

Thanks!

https://codereview.appspot.com/54570048/diff/20001/ec2/ec2.go
File ec2/ec2.go (right):

https://codereview.appspot.com/54570048/diff/20001/ec2/ec2.go#newcode635
ec2/ec2.go:635: // CreateSecurityGroup run a CreateSecurityGroup request
in EC2, with
s/run/runs/

https://codereview.appspot.com/54570048/diff/20001/ec2/ec2.go#newcode643
ec2/ec2.go:643: // CreateSecurityGroupVPC creates a security group in
EC2, associated
s/,//

// If vpcId is empty, this call is equivalent to CreateSecurityGroup.

?

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go
File ec2/networkinterfaces.go (right):

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode80
ec2/networkinterfaces.go:80: // You can specify a primary private IP
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://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode84
ec2/networkinterfaces.go:84: // If you don't specify a private IP
address, EC2 selects one for you
// If a private IP address is not specified, EC2 selects
// one from the subnet range.

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode87
ec2/networkinterfaces.go:87: // SecondaryPrivateIPsCount is the number
of secondary private IP
// When SecondaryPrivateIPsCount is non-zero,
// 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://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode149
ec2/networkinterfaces.go:149: // You must detach the network interface
before you can delete it.
// DeleteNetworkInterface deletes the specified network interface,
// which must have been previously detached.

?

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode192
ec2/networkinterfaces.go:192: // AttachNetworkInterfaceResp is the
response to a
s/ a/ an/

https://codereview.appspot.com/54570048/

lp:~dimitern/goamz/nic-api-calls updated
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

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

Please take a look.

https://codereview.appspot.com/54570048/diff/20001/ec2/ec2.go
File ec2/ec2.go (right):

https://codereview.appspot.com/54570048/diff/20001/ec2/ec2.go#newcode635
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://codereview.appspot.com/54570048/diff/20001/ec2/ec2.go#newcode643
ec2/ec2.go:643: // CreateSecurityGroupVPC creates a security group in
EC2, associated
On 2014/02/05 15:17:35, rog wrote:
> s/,//

> // If vpcId is empty, this call is equivalent to CreateSecurityGroup.

> ?

Done.

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go
File ec2/networkinterfaces.go (right):

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode80
ec2/networkinterfaces.go:80: // You can specify a primary private IP
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{{Address: "..", IsPrimary:
true}}. But perhaps the slightly more verbose syntax will work well to
hide the API complexity.

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode84
ec2/networkinterfaces.go:84: // If you don't specify a private IP
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://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode87
ec2/networkinterfaces.go:87: // SecondaryPrivateIPsCount is the number
of secondary private IP
On 2014/02/05 15:17:35, rog wrote:
> // When SecondaryPrivateIPsCount is non-zero,
> // 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://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode149
ec2/networkinterfaces.go:149: // You must detach the network interface
before you can delete it.
On 2014/02/05 15:17:35, rog wrote:
> // DeleteNetworkInterface deletes the specified network interface,
> // which must have been previously detached.

> ?

Done.

https://codereview.appspot.com/54570048/diff/20001/ec2/networkinterfaces.go#newcode192
ec2/networkinterfaces.go:192: // AttachNetworkInterfaceResp is the
response to a
On 2014/02/05 15:17:35, rog wrote:
> s/ a/ an/

Done.

https://codereview.appspot.com/54570048/

lp:~dimitern/goamz/nic-api-calls updated
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

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

LGTM

https://codereview.appspot.com/54570048/diff/60001/ec2/ec2.go
File ec2/ec2.go (right):

https://codereview.appspot.com/54570048/diff/60001/ec2/ec2.go#newcode637
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://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go
File ec2/networkinterfaces.go (right):

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

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode52
ec2/networkinterfaces.go:52: // NetworkInterface describes a network
interface for AWS VPC.
s/ AWS//

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode78
ec2/networkinterfaces.go:78: // Only the SubnetId is required, the rest
are optional.
// SubnetId is the only required field.

This already implies that the rest is optional.

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode81
ec2/networkinterfaces.go:81: // PrivateIPs slice. Only one of them can
be set as primary.
// Only one provided PrivateIP may be set as primary.

It's implicit that one or more may be provided.

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode93
ec2/networkinterfaces.go:93: SecondaryPrivateIPsCount int
s/IPsCount/IPCount/? That seems to read better, and also matches the
plural form used by the underlying EC2 parameter.

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode163
ec2/networkinterfaces.go:163: // interfaces to the matching ids or
filtering rules.
// NetworkInterfaces returns a list of network interfaces.
//
// If the ids or filter parameters are provided, only matching
interfaces
// are returned.

https://codereview.appspot.com/54570048/diff/60001/ec2/subnets_test.go
File ec2/subnets_test.go (right):

https://codereview.appspot.com/54570048/diff/60001/ec2/subnets_test.go#newcode94
ec2/subnets_test.go:94: // Subnet tests to run against either a local
test server or live on EC2.
s/ to// -- the original sentence was correct ("These tests run
against...")

https://codereview.appspot.com/54570048/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Download full text (3.8 KiB)

*** Submitted:

ec2: Add VPC NetworkInterface-related APIs

Added the following new API calls:
- CreateNetworkInterface
- DeleteNetworkInterface
- NetworkInterfaces
- AttachNetworkInterface
- DetachNetworkInterface
(and related types/responses)

Modified existing calls/types:
- SecurityGroupInfo now includes VPCId field
- Add CreateSecurityGroupVPC call that does
the same as CreateSecurityGroup, but sets
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://codereview.appspot.com/54570048

https://codereview.appspot.com/54570048/diff/60001/ec2/ec2.go
File ec2/ec2.go (right):

https://codereview.appspot.com/54570048/diff/60001/ec2/ec2.go#newcode637
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://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go
File ec2/networkinterfaces.go (right):

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode8
ec2/networkinterfaces.go:8: // Written by Gustavo Niemeyer
<email address hidden>
On 2014/02/12 14:22:38, niemeyer wrote:
> Please drop this line.

Done.

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode52
ec2/networkinterfaces.go:52: // NetworkInterface describes a network
interface for AWS VPC.
On 2014/02/12 14:22:38, niemeyer wrote:
> s/ AWS//

Done.

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode78
ec2/networkinterfaces.go:78: // Only the SubnetId is required, the rest
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://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode81
ec2/networkinterfaces.go:81: // PrivateIPs slice. Only one of them can
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://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode93
ec2/networkinterfaces.go:93: SecondaryPrivateIPsCount int
On 2014/02/12 14:22:38, niemeyer wrote:
> s/IPsCount/IPCount/? That seems to read better, and also matches the
plural
> form used by the underlying EC2 parameter.

Done. Also, as agreed on IRC, I'll rename NetworkInterfaceOptions to
CreateNetworkInterface.

https://codereview.appspot.com/54570048/diff/60001/ec2/networkinterfaces.go#newcode163
ec2/networkinterfaces.go:163: // interfaces to the matching ids or
filter...

Read more...

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

Post-submit approval.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ec2/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)

Subscribers

People subscribed via source and target branches