Merge lp:~gz/goamz/tests_amazon_region into lp:goamz

Proposed by Martin Packman
Status: Needs review
Proposed branch: lp:~gz/goamz/tests_amazon_region
Merge into: lp:goamz
Diff against target: 501 lines (+311/-29)
7 files modified
ec2/ec2_test.go (+7/-7)
ec2/ec2i_test.go (+17/-7)
ec2/ec2t_test.go (+21/-9)
ec2/ec2test/server.go (+1/-1)
testutil/streams.go (+91/-0)
testutil/streams_test.go (+150/-0)
testutil/suite.go (+24/-5)
To merge this branch: bzr merge lp:~gz/goamz/tests_amazon_region
Reviewer Review Type Date Requested Status
goamz maintainers Pending
Review via email: mp+192269@code.launchpad.net

Description of the change

Enable running live tests against any EC2 region

There are two stages:
 * Making the test -amazon flag take a region name
 * Looking up real amis with simplestreams and using those in tests

Perhaps leaving the -amazon flag as an enabling bool, and setting a new
flag just for selecting a region that defaults to us-east-1 would be
better.

Both the simplestreams code and required test changes are pretty ugly,
a bigger rewrite would help to clean up the tests.

https://codereview.appspot.com/14930049/

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Reviewers: mp+192269_code.launchpad.net,

Message:
Please take a look.

Description:
Enable running live tests against any EC2 region

There are two stages:
  * Making the test -amazon flag take a region name
  * Looking up real amis with simplestreams and using those in tests

Perhaps leaving the -amazon flag as an enabling bool, and setting a new
flag just for selecting a region that defaults to us-east-1 would be
better.

Both the simplestreams code and required test changes are pretty ugly,
a bigger rewrite would help to clean up the tests.

https://code.launchpad.net/~gz/goamz/tests_amazon_region/+merge/192269

(do not edit description out of merge proposal)

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

Affected files (+304, -19 lines):
   A [revision details]
   M ec2/ec2i_test.go
   M ec2/ec2t_test.go
   A testutil/streams.go
   A testutil/streams_test.go
   M testutil/suite.go

lp:~gz/goamz/tests_amazon_region updated
46. By Martin Packman

go fmt

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

Unmerged revisions

46. By Martin Packman

go fmt

45. By Martin Packman

Get real ami values for live tests from published simplestreams data

44. By Martin Packman

Change -amazon test flag to accept region value for testing against other regions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ec2/ec2_test.go'
2--- ec2/ec2_test.go 2013-10-04 17:05:49 +0000
3+++ ec2/ec2_test.go 2013-10-24 00:16:37 +0000
4@@ -96,14 +96,14 @@
5 DisableAPITermination: true,
6 ShutdownBehavior: "terminate",
7 PrivateIPAddress: "10.0.0.25",
8- BlockDeviceMappings: []ec2.BlockDeviceMapping{{
9- DeviceName: "device-name",
10- VirtualName: "virtual-name",
11- SnapshotId: "snapshot-id",
12- VolumeType: "volume-type",
13- VolumeSize: 10,
14+ BlockDeviceMappings: []ec2.BlockDeviceMapping{{
15+ DeviceName: "device-name",
16+ VirtualName: "virtual-name",
17+ SnapshotId: "snapshot-id",
18+ VolumeType: "volume-type",
19+ VolumeSize: 10,
20 DeleteOnTermination: true,
21- IOPS: 1000,
22+ IOPS: 1000,
23 }},
24 }
25 resp, err := s.ec2.RunInstances(&options)
26
27=== modified file 'ec2/ec2i_test.go'
28--- ec2/ec2i_test.go 2013-01-31 14:52:05 +0000
29+++ ec2/ec2i_test.go 2013-10-24 00:16:37 +0000
30@@ -32,27 +32,35 @@
31 }
32
33 func (s *AmazonClientSuite) SetUpSuite(c *C) {
34- if !testutil.Amazon {
35+ if testutil.AmazonRegion == nil {
36 c.Skip("AmazonClientSuite tests not enabled")
37 }
38 s.srv.SetUp(c)
39- s.ec2 = ec2.New(s.srv.auth, aws.USEast)
40+ s.ec2 = ec2.New(s.srv.auth, *testutil.AmazonRegion)
41+ s.GetImage = func(version, rootStore string) (string, error) {
42+ return testutil.GetImageForRegion(s.ec2.Region, version, rootStore)
43+ }
44 }
45
46+type imageGetter func(string, string) (string, error)
47+
48 // ClientTests defines integration tests designed to test the client.
49 // It is not used as a test suite in itself, but embedded within
50 // another type.
51 type ClientTests struct {
52- ec2 *ec2.EC2
53+ ec2 *ec2.EC2
54+ GetImage imageGetter
55 }
56
57-var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store
58-
59 // Cost: 0.00 USD
60 func (s *ClientTests) TestRunInstancesError(c *C) {
61+ // Get an image id that used instance store, which can't be used on
62+ // t1.micro machines, results in 400 error from RunInstances.
63+ imageId, err := s.GetImage("12.04", "instance")
64+ c.Assert(err, IsNil)
65 options := ec2.RunInstances{
66- ImageId: "ami-a6f504cf", // Ubuntu Maverick, i386, instance store
67- InstanceType: "t1.micro", // Doesn't work with micro, results in 400.
68+ ImageId: imageId,
69+ InstanceType: "t1.micro",
70 }
71
72 resp, err := s.ec2.RunInstances(&options)
73@@ -70,6 +78,8 @@
74
75 // Cost: 0.02 USD
76 func (s *ClientTests) TestRunAndTerminate(c *C) {
77+ imageId, err := s.GetImage("12.04", "ebs")
78+ c.Assert(err, IsNil)
79 options := ec2.RunInstances{
80 ImageId: imageId,
81 InstanceType: "t1.micro",
82
83=== modified file 'ec2/ec2t_test.go'
84--- ec2/ec2t_test.go 2013-10-02 21:34:26 +0000
85+++ ec2/ec2t_test.go 2013-10-24 00:16:37 +0000
86@@ -46,6 +46,11 @@
87 s.srv.SetUp(c)
88 s.ServerTests.ec2 = ec2.New(s.srv.auth, s.srv.region)
89 s.clientTests.ec2 = ec2.New(s.srv.auth, s.srv.region)
90+ getFakeImage := func(version, rootStore string) (string, error) {
91+ return fmt.Sprintf("ami-fake-%v-%v", version, rootStore), nil
92+ }
93+ s.ServerTests.GetImage = getFakeImage
94+ s.clientTests.GetImage = getFakeImage
95 }
96
97 func (s *LocalServerSuite) TestRunAndTerminate(c *C) {
98@@ -64,7 +69,7 @@
99 data[i] = byte(i)
100 }
101 inst, err := s.ec2.RunInstances(&ec2.RunInstances{
102- ImageId: imageId,
103+ ImageId: "ami-fakeimage",
104 InstanceType: "t1.micro",
105 UserData: data,
106 })
107@@ -82,7 +87,7 @@
108
109 func (s *LocalServerSuite) TestInstanceInfo(c *C) {
110 list, err := s.ec2.RunInstances(&ec2.RunInstances{
111- ImageId: imageId,
112+ ImageId: "ami-fakeimage",
113 InstanceType: "t1.micro",
114 })
115 c.Assert(err, IsNil)
116@@ -113,11 +118,14 @@
117 var _ = Suite(&AmazonServerSuite{})
118
119 func (s *AmazonServerSuite) SetUpSuite(c *C) {
120- if !testutil.Amazon {
121+ if testutil.AmazonRegion == nil {
122 c.Skip("AmazonServerSuite tests not enabled")
123 }
124 s.srv.SetUp(c)
125- s.ServerTests.ec2 = ec2.New(s.srv.auth, aws.USEast)
126+ s.ServerTests.ec2 = ec2.New(s.srv.auth, *testutil.AmazonRegion)
127+ s.GetImage = func(version string, rootStore string) (string, error) {
128+ return testutil.GetImageForRegion(s.ec2.Region, version, rootStore)
129+ }
130 }
131
132 // ServerTests defines a set of tests designed to test
133@@ -125,7 +133,8 @@
134 // It is not used as a test suite in itself, but embedded within
135 // another type.
136 type ServerTests struct {
137- ec2 *ec2.EC2
138+ ec2 *ec2.EC2
139+ GetImage imageGetter
140 }
141
142 func terminateInstances(c *C, e *ec2.EC2, insts []*ec2.Instance) {
143@@ -313,10 +322,12 @@
144 group2 := groupResp.SecurityGroup
145 defer s.ec2.DeleteSecurityGroup(group2)
146
147+ imageId1, err := s.GetImage("12.04", "ebs")
148+ c.Assert(err, IsNil)
149 insts := make([]*ec2.Instance, 3)
150 inst, err := s.ec2.RunInstances(&ec2.RunInstances{
151 MinCount: 2,
152- ImageId: imageId,
153+ ImageId: imageId1,
154 InstanceType: "t1.micro",
155 SecurityGroups: []ec2.SecurityGroup{group1},
156 })
157@@ -325,7 +336,8 @@
158 insts[1] = &inst.Instances[1]
159 defer terminateInstances(c, s.ec2, insts)
160
161- imageId2 := "ami-e358958a" // Natty server, i386, EBS store
162+ imageId2, err := s.GetImage("13.10", "ebs")
163+ c.Assert(err, IsNil)
164 inst, err = s.ec2.RunInstances(&ec2.RunInstances{
165 ImageId: imageId2,
166 InstanceType: "t1.micro",
167@@ -405,14 +417,14 @@
168 }, {
169 about: "check that filtering on image id works",
170 filters: []filterSpec{
171- {"image-id", []string{imageId}},
172+ {"image-id", []string{imageId1}},
173 },
174 resultIds: ids(0, 1),
175 allowExtra: true,
176 }, {
177 about: "combination filters 1",
178 filters: []filterSpec{
179- {"image-id", []string{imageId, imageId2}},
180+ {"image-id", []string{imageId1, imageId2}},
181 {"group-name", []string{group1.Name}},
182 },
183 resultIds: ids(0, 1),
184
185=== modified file 'ec2/ec2test/server.go'
186--- ec2/ec2test/server.go 2013-10-03 12:10:33 +0000
187+++ ec2/ec2test/server.go 2013-10-24 00:16:37 +0000
188@@ -66,7 +66,7 @@
189
190 // instance holds a simulated ec2 instance
191 type Instance struct {
192- seq int
193+ seq int
194 // UserData holds the data that was passed to the RunInstances request
195 // when the instance was started.
196 UserData []byte
197
198=== added file 'testutil/streams.go'
199--- testutil/streams.go 1970-01-01 00:00:00 +0000
200+++ testutil/streams.go 2013-10-24 00:16:37 +0000
201@@ -0,0 +1,91 @@
202+package testutil
203+
204+import (
205+ "encoding/json"
206+ "fmt"
207+ "io/ioutil"
208+ "launchpad.net/goamz/aws"
209+ "net/http"
210+)
211+
212+var streamsURL = "http://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:aws.json"
213+
214+func fetchStreamsData(result interface{}) error {
215+ response, err := http.Get(streamsURL)
216+ if err != nil {
217+ return fmt.Errorf("failed to fetch streams data: %v", err)
218+ }
219+ defer response.Body.Close()
220+ contents, err := ioutil.ReadAll(response.Body)
221+ if err != nil {
222+ return fmt.Errorf("failed to read streams data: %v", err)
223+ }
224+ if response.StatusCode != 200 {
225+ return fmt.Errorf("http error fetching streams data: %q", contents)
226+ }
227+ err = json.Unmarshal(contents, &result)
228+ if err != nil {
229+ return fmt.Errorf("failed to parse streams data: %v", err)
230+ }
231+ return nil
232+}
233+
234+// TODO(gz): These structs are based on those in lp:juju-core simplestreams
235+// code at environs/simplestreams/simplestreams.go which if split out into a
236+// seperate module could be reused here instead.
237+
238+type CloudMetadata struct {
239+ Products map[string]MetadataCatalog `json:"products"`
240+ DataType string `json:"datatype"`
241+}
242+
243+type MetadataCatalog struct {
244+ Series string `json:"release,omitempty"`
245+ Version string `json:"version,omitempty"`
246+ Arch string `json:"arch,omitempty"`
247+
248+ Versions map[string]ItemCollection `json:"versions"`
249+}
250+
251+type ItemCollection struct {
252+ Items map[string]Item `json:"items"`
253+}
254+
255+type Item struct {
256+ RootStore string `json:"root_store"`
257+ Virt string `json:"virt"`
258+ CRSN string `json:"crsn"`
259+ Id string `json:"id"`
260+}
261+
262+var streamsData *CloudMetadata
263+
264+// GetImageForRegion selects an ami string id from the published simplestreams
265+// data matching the region, version, and root store values given.
266+func GetImageForRegion(region aws.Region, version string, rootStore string) (string, error) {
267+ if streamsData == nil {
268+ err := fetchStreamsData(&streamsData)
269+ if err != nil {
270+ return "", err
271+ }
272+ }
273+ for productKey, _ := range streamsData.Products {
274+ product := streamsData.Products[productKey]
275+ if version != product.Version {
276+ continue
277+ }
278+ for versionsKey, _ := range product.Versions {
279+ itemCollection := product.Versions[versionsKey]
280+ for itemKey, _ := range itemCollection.Items {
281+ item := itemCollection.Items[itemKey]
282+ if item.Virt == "hvm" || item.RootStore != rootStore {
283+ continue
284+ }
285+ if item.CRSN == region.Name {
286+ return item.Id, nil
287+ }
288+ }
289+ }
290+ }
291+ return "", fmt.Errorf("no image found for region:%v version:%v root_store:%v", region.Name, version, rootStore)
292+}
293
294=== added file 'testutil/streams_test.go'
295--- testutil/streams_test.go 1970-01-01 00:00:00 +0000
296+++ testutil/streams_test.go 2013-10-24 00:16:37 +0000
297@@ -0,0 +1,150 @@
298+package testutil
299+
300+import (
301+ "encoding/json"
302+ "testing"
303+
304+ gc "launchpad.net/gocheck"
305+
306+ "launchpad.net/goamz/aws"
307+)
308+
309+func Test(t *testing.T) {
310+ gc.TestingT(t)
311+}
312+
313+func init() {
314+ gc.Suite(&StreamsServerSuite{NewHTTPServer()})
315+ gc.Suite(&StreamsSuite{})
316+}
317+
318+type StreamsSuite struct{}
319+
320+type StreamsServerSuite struct {
321+ Server *HTTPServer
322+}
323+
324+func (s *StreamsServerSuite) SetUpSuite(c *gc.C) {
325+ s.Server.Start()
326+}
327+
328+func (s *StreamsServerSuite) TearDownTest(c *gc.C) {
329+ s.Server.Flush()
330+}
331+
332+func (s *StreamsServerSuite) TearDownSuite(c *gc.C) {
333+ s.Server.Stop()
334+}
335+
336+func (s *StreamsServerSuite) patchStreamsURL() func() {
337+ origStreamsURL := streamsURL
338+ streamsURL = s.Server.URL
339+ return func() {
340+ streamsURL = origStreamsURL
341+ }
342+}
343+
344+func (s *StreamsServerSuite) TestFetchStreams(c *gc.C) {
345+ s.Server.Response(200, nil, "{}")
346+
347+ defer s.patchStreamsURL()()
348+
349+ result := make(map[string]interface{})
350+ err := fetchStreamsData(&result)
351+ c.Assert(err, gc.IsNil)
352+ c.Check(result, gc.DeepEquals, map[string]interface{}{})
353+}
354+
355+func (s *StreamsServerSuite) TestFetchStreamsServerError(c *gc.C) {
356+ s.Server.Response(500, nil, "Server error")
357+
358+ defer s.patchStreamsURL()()
359+
360+ result := make(map[string]interface{})
361+ err := fetchStreamsData(&result)
362+ c.Assert(err, gc.NotNil)
363+ c.Assert(err, gc.ErrorMatches, "http error.*Server error.*")
364+}
365+
366+func (s *StreamsServerSuite) TestFetchStreamsBadJSON(c *gc.C) {
367+ s.Server.Response(200, nil, "--junk-- {}")
368+
369+ defer s.patchStreamsURL()()
370+
371+ result := make(map[string]interface{})
372+ err := fetchStreamsData(&result)
373+ c.Assert(err, gc.NotNil)
374+ c.Assert(err, gc.ErrorMatches, "failed to parse streams data: .*")
375+}
376+
377+func (s *StreamsSuite) patchStreamsData(data string) func() {
378+ cloudData := CloudMetadata{}
379+ err := json.Unmarshal([]byte(data), &cloudData)
380+ if err != nil {
381+ panic(err)
382+ }
383+ streamsData = &cloudData
384+ return func() {
385+ streamsData = nil
386+ }
387+}
388+
389+var sampleStreamsData = `{
390+ "updated": "Sun, 20 Oct 2013 13:48:35 +0000",
391+ "format": "products:1.0",
392+ "datatype": "image-ids",
393+ "products": {
394+ "com.ubuntu.cloud:server:13.10:amd64": {
395+ "release": "saucy",
396+ "version": "13.10",
397+ "arch": "amd64",
398+ "versions": {
399+ "20130808": {
400+ "items": {
401+ "usww1pe": {
402+ "root_store": "ebs",
403+ "virt": "pv",
404+ "crsn": "us-west-1",
405+ "id": "ami-88ffd4cd"
406+ },
407+ "usww2he": {
408+ "root_store": "ebs",
409+ "virt": "hvm",
410+ "crsn": "us-west-2",
411+ "id": "ami-89f964b9"
412+ },
413+ "apne1pe": {
414+ "root_store": "ebs",
415+ "virt": "pv",
416+ "crsn": "ap-northeast-1",
417+ "id": "ami-1139a810"
418+ },
419+ "usee1pi": {
420+ "root_store": "instance",
421+ "virt": "pv",
422+ "crsn": "us-east-1",
423+ "id": "ami-eaafe883"
424+ }
425+ }
426+ }
427+ }
428+ }
429+ }
430+}
431+`
432+
433+func (s *StreamsSuite) TestFetchStreamsUSEast(c *gc.C) {
434+ defer s.patchStreamsData(sampleStreamsData)()
435+
436+ imageId, err := GetImageForRegion(aws.USEast, "13.10", "ebs")
437+ c.Assert(err, gc.IsNil)
438+ c.Assert(imageId, gc.Equals, "ami-eaafe883")
439+}
440+
441+func (s *StreamsSuite) TestFetchStreamsBadJSON(c *gc.C) {
442+ defer s.patchStreamsData("{}")()
443+
444+ _, err := GetImageForRegion(aws.USEast, "13.10", "ebs")
445+ c.Assert(err, gc.NotNil)
446+ c.Assert(err, gc.ErrorMatches, "no image found for region.*")
447+}
448
449=== modified file 'testutil/suite.go'
450--- testutil/suite.go 2013-01-31 14:52:05 +0000
451+++ testutil/suite.go 2013-10-24 00:16:37 +0000
452@@ -1,17 +1,36 @@
453 package testutil
454
455 import (
456+ "errors"
457 "flag"
458 "launchpad.net/goamz/aws"
459 . "launchpad.net/gocheck"
460 )
461
462-// Amazon must be used by all tested packages to determine whether to
463-// run functional tests against the real AWS servers.
464-var Amazon bool
465+// AmazonRegion must be used by all tested packages to determine which region
466+// to use when runnong functional tests against the real AWS servers, and
467+// disable those tests if nil.
468+var AmazonRegion *aws.Region
469+
470+type amazonRegionValue struct {
471+ flag.Value
472+}
473+
474+func (v *amazonRegionValue) String() string {
475+ return "REGION"
476+}
477+
478+func (v *amazonRegionValue) Set(val string) error {
479+ region, ok := aws.Regions[val]
480+ if !ok {
481+ return errors.New("not an AWS region")
482+ }
483+ AmazonRegion = &region
484+ return nil
485+}
486
487 func init() {
488- flag.BoolVar(&Amazon, "amazon", false, "Enable tests against amazon server")
489+ flag.Var(&amazonRegionValue{}, "amazon", "Region to enable tests against amazon server")
490 }
491
492 type LiveSuite struct {
493@@ -19,7 +38,7 @@
494 }
495
496 func (s *LiveSuite) SetUpSuite(c *C) {
497- if !Amazon {
498+ if AmazonRegion == nil {
499 c.Skip("amazon tests not enabled (-amazon flag)")
500 }
501 auth, err := aws.EnvAuth()

Subscribers

People subscribed via source and target branches