1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
19
"github.com/juju/errors"
20
gitjujutesting "github.com/juju/testing"
21
jc "github.com/juju/testing/checkers"
22
"github.com/juju/utils"
23
"github.com/juju/utils/arch"
24
"github.com/juju/utils/series"
25
"github.com/juju/version"
26
gc "gopkg.in/check.v1"
28
"github.com/juju/juju/environs"
29
"github.com/juju/juju/environs/filestorage"
30
"github.com/juju/juju/environs/simplestreams"
31
"github.com/juju/juju/environs/storage"
32
"github.com/juju/juju/environs/sync"
33
envtesting "github.com/juju/juju/environs/testing"
34
envtools "github.com/juju/juju/environs/tools"
35
toolstesting "github.com/juju/juju/environs/tools/testing"
36
coretesting "github.com/juju/juju/testing"
37
coretools "github.com/juju/juju/tools"
38
jujuversion "github.com/juju/juju/version"
41
func TestPackage(t *testing.T) {
45
type syncSuite struct {
46
coretesting.FakeJujuXDGDataHomeSuite
47
envtesting.ToolsFixture
48
storage storage.Storage
52
var _ = gc.Suite(&syncSuite{})
53
var _ = gc.Suite(&uploadSuite{})
54
var _ = gc.Suite(&badBuildSuite{})
56
func (s *syncSuite) setUpTest(c *gc.C) {
57
if runtime.GOOS == "windows" {
58
c.Skip("issue 1403084: Currently does not work because of jujud problems")
60
s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
61
s.ToolsFixture.SetUpTest(c)
63
// It's important that this be v1.8.x to match the test data.
64
s.PatchValue(&jujuversion.Current, version.MustParse("1.8.3"))
66
// Create a source storage.
68
stor, err := filestorage.NewFileStorageWriter(baseDir)
69
c.Assert(err, jc.ErrorIsNil)
72
// Create a local tools directory.
73
s.localStorage = c.MkDir()
75
// Populate both local and default tools locations with the public tools.
76
versionStrings := make([]string, len(vAll))
77
for i, vers := range vAll {
78
versionStrings[i] = vers.String()
80
toolstesting.MakeTools(c, baseDir, "released", versionStrings)
81
toolstesting.MakeTools(c, s.localStorage, "released", versionStrings)
83
// Switch the default tools location.
84
baseURL, err := s.storage.URL(storage.BaseToolsPath)
85
c.Assert(err, jc.ErrorIsNil)
86
s.PatchValue(&envtools.DefaultBaseURL, baseURL)
89
func (s *syncSuite) tearDownTest(c *gc.C) {
90
s.ToolsFixture.TearDownTest(c)
91
s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
94
var tests = []struct {
98
tools []version.Binary
99
version version.Number
104
description: "copy newest from the filesystem",
105
ctx: &sync.SyncContext{},
110
description: "copy newest from the dummy model",
111
ctx: &sync.SyncContext{},
115
description: "copy matching dev from the dummy model",
116
ctx: &sync.SyncContext{},
117
version: version.MustParse("1.9.3"),
121
description: "copy matching major, minor from the dummy model",
122
ctx: &sync.SyncContext{},
125
tools: []version.Binary{v320p64},
128
description: "copy matching major, minor dev from the dummy model",
129
ctx: &sync.SyncContext{},
132
tools: []version.Binary{v310p64},
135
description: "copy all from the dummy model",
136
ctx: &sync.SyncContext{
143
func (s *syncSuite) TestSyncing(c *gc.C) {
144
for i, test := range tests {
145
// Perform all tests in a "clean" environment.
148
defer s.tearDownTest(c)
150
c.Logf("test %d: %s", i, test.description)
153
test.ctx.Source = s.localStorage
155
if test.version != version.Zero {
156
jujuversion.Current = test.version
159
test.ctx.MajorVersion = test.major
160
test.ctx.MinorVersion = test.minor
162
uploader := fakeToolsUploader{
163
uploaded: make(map[version.Binary]bool),
165
test.ctx.TargetToolsFinder = mockToolsFinder{}
166
test.ctx.TargetToolsUploader = &uploader
168
err := sync.SyncTools(test.ctx)
169
c.Assert(err, jc.ErrorIsNil)
171
ds, err := sync.SelectSourceDatasource(test.ctx)
172
c.Assert(err, jc.ErrorIsNil)
174
// This data source does not require to contain signed data.
175
// However, it may still contain it.
176
// Since we will always try to read signed data first,
177
// we want to be able to try to read this signed data
178
// with public key with Juju-known public key for tools.
179
// Bugs #1542127, #1542131
180
c.Assert(ds.PublicSigningKey(), gc.Not(gc.Equals), "")
182
var uploaded []version.Binary
183
for v := range uploader.uploaded {
184
uploaded = append(uploaded, v)
186
c.Assert(uploaded, jc.SameContents, test.tools)
191
type fakeToolsUploader struct {
192
uploaded map[version.Binary]bool
195
func (u *fakeToolsUploader) UploadTools(toolsDir, stream string, tools *coretools.Tools, data []byte) error {
196
u.uploaded[tools.Version] = true
201
v100p64 = version.MustParseBinary("1.0.0-precise-amd64")
202
v100q64 = version.MustParseBinary("1.0.0-quantal-amd64")
203
v100q32 = version.MustParseBinary("1.0.0-quantal-i386")
204
v100all = []version.Binary{v100p64, v100q64, v100q32}
205
v180q64 = version.MustParseBinary("1.8.0-quantal-amd64")
206
v180p32 = version.MustParseBinary("1.8.0-precise-i386")
207
v180all = []version.Binary{v180q64, v180p32}
208
v190q64 = version.MustParseBinary("1.9.0-quantal-amd64")
209
v190p32 = version.MustParseBinary("1.9.0-precise-i386")
210
v190all = []version.Binary{v190q64, v190p32}
211
v1all = append(append(v100all, v180all...), v190all...)
212
v200p64 = version.MustParseBinary("2.0.0-precise-amd64")
213
v310p64 = version.MustParseBinary("3.1.0-precise-amd64")
214
v320p64 = version.MustParseBinary("3.2.0-precise-amd64")
215
vAll = append(append(v1all, v200p64), v310p64, v320p64)
218
type uploadSuite struct {
220
coretesting.FakeJujuXDGDataHomeSuite
221
envtesting.ToolsFixture
222
targetStorage storage.Storage
225
func (s *uploadSuite) SetUpTest(c *gc.C) {
226
if runtime.GOOS == "windows" {
227
c.Skip("issue 1403084: Currently does not work because of jujud problems")
229
s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
230
s.ToolsFixture.SetUpTest(c)
232
// Create a target storage.
233
stor, err := filestorage.NewFileStorageWriter(c.MkDir())
234
c.Assert(err, jc.ErrorIsNil)
235
s.targetStorage = stor
237
// Mock out building of tools. Sync should not care about the contents
238
// of tools archives, other than that they hash correctly.
239
s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
242
func (s *uploadSuite) assertEqualsCurrentVersion(c *gc.C, v version.Binary) {
243
c.Assert(v, gc.Equals, version.Binary{Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries()})
246
func (s *uploadSuite) TearDownTest(c *gc.C) {
247
s.ToolsFixture.TearDownTest(c)
248
s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
251
func (s *uploadSuite) TestUpload(c *gc.C) {
252
t, err := sync.Upload(s.targetStorage, "released", nil)
253
c.Assert(err, jc.ErrorIsNil)
254
s.assertEqualsCurrentVersion(c, t.Version)
255
c.Assert(t.URL, gc.Not(gc.Equals), "")
256
s.assertUploadedTools(c, t, []string{series.HostSeries()}, "released")
259
func (s *uploadSuite) TestUploadFakeSeries(c *gc.C) {
260
seriesToUpload := "precise"
261
if seriesToUpload == series.HostSeries() {
262
seriesToUpload = "raring"
264
t, err := sync.Upload(s.targetStorage, "released", nil, "quantal", seriesToUpload)
265
c.Assert(err, jc.ErrorIsNil)
266
s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "released")
269
func (s *uploadSuite) TestUploadAndForceVersion(c *gc.C) {
270
// This test actually tests three things:
271
// the writing of the FORCE-VERSION file;
272
// the reading of the FORCE-VERSION file by the version package;
273
// and the reading of the version from jujud.
274
vers := jujuversion.Current
276
t, err := sync.Upload(s.targetStorage, "released", &vers)
277
c.Assert(err, jc.ErrorIsNil)
278
c.Assert(t.Version, gc.Equals, version.Binary{Number: vers, Arch: arch.HostArch(), Series: series.HostSeries()})
281
func (s *uploadSuite) TestSyncTools(c *gc.C) {
282
builtTools, err := sync.BuildToolsTarball(nil, "released")
283
c.Assert(err, jc.ErrorIsNil)
284
t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools)
285
c.Assert(err, jc.ErrorIsNil)
286
s.assertEqualsCurrentVersion(c, t.Version)
287
c.Assert(t.URL, gc.Not(gc.Equals), "")
290
func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) {
291
seriesToUpload := "precise"
292
if seriesToUpload == series.HostSeries() {
293
seriesToUpload = "raring"
295
builtTools, err := sync.BuildToolsTarball(nil, "testing")
296
c.Assert(err, jc.ErrorIsNil)
298
t, err := sync.SyncBuiltTools(s.targetStorage, "testing", builtTools, "quantal", seriesToUpload)
299
c.Assert(err, jc.ErrorIsNil)
300
s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "testing")
303
func (s *uploadSuite) TestSyncAndForceVersion(c *gc.C) {
304
// This test actually tests three things:
305
// the writing of the FORCE-VERSION file;
306
// the reading of the FORCE-VERSION file by the version package;
307
// and the reading of the version from jujud.
308
vers := jujuversion.Current
310
builtTools, err := sync.BuildToolsTarball(&vers, "released")
311
c.Assert(err, jc.ErrorIsNil)
312
t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools)
313
c.Assert(err, jc.ErrorIsNil)
314
c.Assert(t.Version, gc.Equals, version.Binary{Number: vers, Arch: arch.HostArch(), Series: series.HostSeries()})
317
func (s *uploadSuite) assertUploadedTools(c *gc.C, t *coretools.Tools, expectSeries []string, stream string) {
318
s.assertEqualsCurrentVersion(c, t.Version)
319
expectRaw := downloadToolsRaw(c, t)
321
list, err := envtools.ReadList(s.targetStorage, stream, jujuversion.Current.Major, jujuversion.Current.Minor)
322
c.Assert(err, jc.ErrorIsNil)
323
c.Assert(list.AllSeries(), jc.SameContents, expectSeries)
324
sort.Strings(expectSeries)
325
c.Assert(list.AllSeries(), gc.DeepEquals, expectSeries)
326
for _, t := range list {
327
c.Logf("checking %s", t.URL)
328
c.Assert(t.Version.Number, gc.Equals, jujuversion.Current)
329
actualRaw := downloadToolsRaw(c, t)
330
c.Assert(string(actualRaw), gc.Equals, string(expectRaw))
332
metadata, err := envtools.ReadMetadata(s.targetStorage, stream)
333
c.Assert(err, jc.ErrorIsNil)
334
c.Assert(metadata, gc.HasLen, 0)
337
// downloadToolsRaw downloads the supplied tools and returns the raw bytes.
338
func downloadToolsRaw(c *gc.C, t *coretools.Tools) []byte {
339
resp, err := utils.GetValidatingHTTPClient().Get(t.URL)
340
c.Assert(err, jc.ErrorIsNil)
341
defer resp.Body.Close()
342
c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
344
_, err = io.Copy(&buf, resp.Body)
345
c.Assert(err, jc.ErrorIsNil)
349
func bundleTools(c *gc.C) (version.Binary, string, error) {
350
f, err := ioutil.TempFile("", "juju-tgz")
351
c.Assert(err, jc.ErrorIsNil)
353
defer os.Remove(f.Name())
355
return envtools.BundleTools(f, &jujuversion.Current)
358
type badBuildSuite struct {
360
gitjujutesting.LoggingSuite
361
gitjujutesting.CleanupSuite
362
envtesting.ToolsFixture
370
func (s *badBuildSuite) SetUpSuite(c *gc.C) {
371
if runtime.GOOS == "windows" {
372
c.Skip("issue 1403084: Currently does not work because of jujud problems")
374
s.CleanupSuite.SetUpSuite(c)
375
s.LoggingSuite.SetUpSuite(c)
378
func (s *badBuildSuite) TearDownSuite(c *gc.C) {
379
s.LoggingSuite.TearDownSuite(c)
380
s.CleanupSuite.TearDownSuite(c)
383
func (s *badBuildSuite) SetUpTest(c *gc.C) {
384
s.CleanupSuite.SetUpTest(c)
385
s.LoggingSuite.SetUpTest(c)
386
s.ToolsFixture.SetUpTest(c)
389
testPath := c.MkDir()
390
s.PatchEnvPathPrepend(testPath)
391
path := filepath.Join(testPath, "go")
392
err := ioutil.WriteFile(path, []byte(badGo), 0755)
393
c.Assert(err, jc.ErrorIsNil)
395
// Check mocked go cmd errors
396
out, err := exec.Command("go").CombinedOutput()
397
c.Assert(err, gc.ErrorMatches, "exit status 1")
398
c.Assert(string(out), gc.Equals, "")
401
func (s *badBuildSuite) TearDownTest(c *gc.C) {
402
s.ToolsFixture.TearDownTest(c)
403
s.LoggingSuite.TearDownTest(c)
404
s.CleanupSuite.TearDownTest(c)
407
func (s *badBuildSuite) assertEqualsCurrentVersion(c *gc.C, v version.Binary) {
408
current := version.Binary{
409
Number: jujuversion.Current,
410
Arch: arch.HostArch(),
411
Series: series.HostSeries(),
413
c.Assert(v, gc.Equals, current)
416
func (s *badBuildSuite) TestBundleToolsBadBuild(c *gc.C) {
417
// Test that original bundleTools Func fails as expected
418
vers, sha256Hash, err := bundleTools(c)
419
c.Assert(vers, gc.DeepEquals, version.Binary{})
420
c.Assert(sha256Hash, gc.Equals, "")
421
c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `)
423
s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
425
// Test that BundleTools func passes after it is
427
vers, sha256Hash, err = bundleTools(c)
428
c.Assert(err, jc.ErrorIsNil)
429
c.Assert(vers.Number, gc.Equals, jujuversion.Current)
430
c.Assert(sha256Hash, gc.Equals, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
433
func (s *badBuildSuite) TestUploadToolsBadBuild(c *gc.C) {
434
stor, err := filestorage.NewFileStorageWriter(c.MkDir())
435
c.Assert(err, jc.ErrorIsNil)
437
// Test that original Upload Func fails as expected
438
t, err := sync.Upload(stor, "released", nil)
439
c.Assert(t, gc.IsNil)
440
c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `)
442
// Test that Upload func passes after BundleTools func is mocked out
443
s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
444
t, err = sync.Upload(stor, "released", nil)
445
c.Assert(err, jc.ErrorIsNil)
446
s.assertEqualsCurrentVersion(c, t.Version)
447
c.Assert(t.URL, gc.Not(gc.Equals), "")
450
func (s *badBuildSuite) TestBuildToolsBadBuild(c *gc.C) {
451
// Test that original BuildToolsTarball fails
452
builtTools, err := sync.BuildToolsTarball(nil, "released")
453
c.Assert(err, gc.ErrorMatches, `build command "go" failed: exit status 1; `)
454
c.Assert(builtTools, gc.IsNil)
456
// Test that BuildToolsTarball func passes after BundleTools func is
458
s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
459
builtTools, err = sync.BuildToolsTarball(nil, "released")
460
s.assertEqualsCurrentVersion(c, builtTools.Version)
461
c.Assert(err, jc.ErrorIsNil)
464
func (s *uploadSuite) TestMockBundleTools(c *gc.C) {
467
forceVersion *version.Number
471
p.WriteString("Hello World")
473
s.PatchValue(&envtools.BundleTools, func(writerArg io.Writer, forceVersionArg *version.Number) (vers version.Binary, sha256Hash string, err error) {
475
n, err = writer.Write(p.Bytes())
476
c.Assert(err, jc.ErrorIsNil)
477
forceVersion = forceVersionArg
481
_, err := sync.BuildToolsTarball(&jujuversion.Current, "released")
482
c.Assert(err, jc.ErrorIsNil)
483
c.Assert(*forceVersion, gc.Equals, jujuversion.Current)
484
c.Assert(writer, gc.NotNil)
485
c.Assert(n, gc.Equals, len(p.Bytes()))
488
func (s *uploadSuite) TestMockBuildTools(c *gc.C) {
489
current := version.MustParseBinary("1.9.1-trusty-amd64")
490
s.PatchValue(&jujuversion.Current, current.Number)
491
s.PatchValue(&arch.HostArch, func() string { return current.Arch })
492
s.PatchValue(&series.HostSeries, func() string { return current.Series })
493
buildToolsFunc := toolstesting.GetMockBuildTools(c)
494
builtTools, err := buildToolsFunc(nil, "released")
495
c.Assert(err, jc.ErrorIsNil)
499
expectedBuiltTools := &sync.BuiltTools{
503
Sha256Hash: "6a19d08ca4913382ca86508aa38eb8ee5b9ae2d74333fe8d862c0f9e29b82c39",
505
c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools)
507
vers := version.MustParseBinary("1.5.3-trusty-amd64")
508
builtTools, err = buildToolsFunc(&vers.Number, "released")
509
c.Assert(err, jc.ErrorIsNil)
511
expectedBuiltTools = &sync.BuiltTools{
515
Sha256Hash: "cad8ccedab8f26807ff379ddc2f2f78d9a7cac1276e001154cee5e39b9ddcc38",
517
c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools)
520
func (s *uploadSuite) TestStorageToolsUploaderWriteMirrors(c *gc.C) {
521
s.testStorageToolsUploaderWriteMirrors(c, envtools.WriteMirrors)
524
func (s *uploadSuite) TestStorageToolsUploaderDontWriteMirrors(c *gc.C) {
525
s.testStorageToolsUploaderWriteMirrors(c, envtools.DoNotWriteMirrors)
528
func (s *uploadSuite) testStorageToolsUploaderWriteMirrors(c *gc.C, writeMirrors envtools.ShouldWriteMirrors) {
529
storageDir := c.MkDir()
530
stor, err := filestorage.NewFileStorageWriter(storageDir)
531
c.Assert(err, jc.ErrorIsNil)
533
uploader := &sync.StorageToolsUploader{
536
WriteMirrors: writeMirrors,
539
err = uploader.UploadTools(
543
Version: version.Binary{
544
Number: jujuversion.Current,
545
Arch: arch.HostArch(),
546
Series: series.HostSeries(),
549
SHA256: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73",
550
}, []byte("content"))
551
c.Assert(err, jc.ErrorIsNil)
553
mirrorsPath := simplestreams.MirrorsPath(envtools.StreamsVersionV1) + simplestreams.UnsignedSuffix
554
r, err := stor.Get(path.Join(storage.BaseToolsPath, mirrorsPath))
555
if writeMirrors == envtools.WriteMirrors {
556
c.Assert(err, jc.ErrorIsNil)
557
data, err := ioutil.ReadAll(r)
559
c.Assert(err, jc.ErrorIsNil)
560
c.Assert(string(data), jc.Contains, `"mirrors":`)
562
c.Assert(err, jc.Satisfies, errors.IsNotFound)
566
type mockToolsFinder struct{}
568
func (mockToolsFinder) FindTools(major int, stream string) (coretools.List, error) {
569
return nil, coretools.ErrNoMatches