github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/environs/sync/sync_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package sync_test
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime"
    16  	"sort"
    17  	"testing"
    18  
    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"
    27  
    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"
    39  )
    40  
    41  func TestPackage(t *testing.T) {
    42  	gc.TestingT(t)
    43  }
    44  
    45  type syncSuite struct {
    46  	coretesting.FakeJujuXDGDataHomeSuite
    47  	envtesting.ToolsFixture
    48  	storage      storage.Storage
    49  	localStorage string
    50  }
    51  
    52  var _ = gc.Suite(&syncSuite{})
    53  var _ = gc.Suite(&uploadSuite{})
    54  var _ = gc.Suite(&badBuildSuite{})
    55  
    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")
    59  	}
    60  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    61  	s.ToolsFixture.SetUpTest(c)
    62  
    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"))
    65  
    66  	// Create a source storage.
    67  	baseDir := c.MkDir()
    68  	stor, err := filestorage.NewFileStorageWriter(baseDir)
    69  	c.Assert(err, jc.ErrorIsNil)
    70  	s.storage = stor
    71  
    72  	// Create a local tools directory.
    73  	s.localStorage = c.MkDir()
    74  
    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()
    79  	}
    80  	toolstesting.MakeTools(c, baseDir, "released", versionStrings)
    81  	toolstesting.MakeTools(c, s.localStorage, "released", versionStrings)
    82  
    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)
    87  }
    88  
    89  func (s *syncSuite) tearDownTest(c *gc.C) {
    90  	s.ToolsFixture.TearDownTest(c)
    91  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    92  }
    93  
    94  var tests = []struct {
    95  	description string
    96  	ctx         *sync.SyncContext
    97  	source      bool
    98  	tools       []version.Binary
    99  	version     version.Number
   100  	major       int
   101  	minor       int
   102  }{
   103  	{
   104  		description: "copy newest from the filesystem",
   105  		ctx:         &sync.SyncContext{},
   106  		source:      true,
   107  		tools:       v180all,
   108  	},
   109  	{
   110  		description: "copy newest from the dummy model",
   111  		ctx:         &sync.SyncContext{},
   112  		tools:       v180all,
   113  	},
   114  	{
   115  		description: "copy matching dev from the dummy model",
   116  		ctx:         &sync.SyncContext{},
   117  		version:     version.MustParse("1.9.3"),
   118  		tools:       v190all,
   119  	},
   120  	{
   121  		description: "copy matching major, minor from the dummy model",
   122  		ctx:         &sync.SyncContext{},
   123  		major:       3,
   124  		minor:       2,
   125  		tools:       []version.Binary{v320p64},
   126  	},
   127  	{
   128  		description: "copy matching major, minor dev from the dummy model",
   129  		ctx:         &sync.SyncContext{},
   130  		major:       3,
   131  		minor:       1,
   132  		tools:       []version.Binary{v310p64},
   133  	},
   134  	{
   135  		description: "copy all from the dummy model",
   136  		ctx: &sync.SyncContext{
   137  			AllVersions: true,
   138  		},
   139  		tools: v1all,
   140  	},
   141  }
   142  
   143  func (s *syncSuite) TestSyncing(c *gc.C) {
   144  	for i, test := range tests {
   145  		// Perform all tests in a "clean" environment.
   146  		func() {
   147  			s.setUpTest(c)
   148  			defer s.tearDownTest(c)
   149  
   150  			c.Logf("test %d: %s", i, test.description)
   151  
   152  			if test.source {
   153  				test.ctx.Source = s.localStorage
   154  			}
   155  			if test.version != version.Zero {
   156  				jujuversion.Current = test.version
   157  			}
   158  			if test.major > 0 {
   159  				test.ctx.MajorVersion = test.major
   160  				test.ctx.MinorVersion = test.minor
   161  			}
   162  			uploader := fakeToolsUploader{
   163  				uploaded: make(map[version.Binary]bool),
   164  			}
   165  			test.ctx.TargetToolsFinder = mockToolsFinder{}
   166  			test.ctx.TargetToolsUploader = &uploader
   167  
   168  			err := sync.SyncTools(test.ctx)
   169  			c.Assert(err, jc.ErrorIsNil)
   170  
   171  			ds, err := sync.SelectSourceDatasource(test.ctx)
   172  			c.Assert(err, jc.ErrorIsNil)
   173  
   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), "")
   181  
   182  			var uploaded []version.Binary
   183  			for v := range uploader.uploaded {
   184  				uploaded = append(uploaded, v)
   185  			}
   186  			c.Assert(uploaded, jc.SameContents, test.tools)
   187  		}()
   188  	}
   189  }
   190  
   191  type fakeToolsUploader struct {
   192  	uploaded map[version.Binary]bool
   193  }
   194  
   195  func (u *fakeToolsUploader) UploadTools(toolsDir, stream string, tools *coretools.Tools, data []byte) error {
   196  	u.uploaded[tools.Version] = true
   197  	return nil
   198  }
   199  
   200  var (
   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)
   216  )
   217  
   218  type uploadSuite struct {
   219  	env environs.Environ
   220  	coretesting.FakeJujuXDGDataHomeSuite
   221  	envtesting.ToolsFixture
   222  	targetStorage storage.Storage
   223  }
   224  
   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")
   228  	}
   229  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
   230  	s.ToolsFixture.SetUpTest(c)
   231  
   232  	// Create a target storage.
   233  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	s.targetStorage = stor
   236  
   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))
   240  }
   241  
   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()})
   244  }
   245  
   246  func (s *uploadSuite) TearDownTest(c *gc.C) {
   247  	s.ToolsFixture.TearDownTest(c)
   248  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
   249  }
   250  
   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")
   257  }
   258  
   259  func (s *uploadSuite) TestUploadFakeSeries(c *gc.C) {
   260  	seriesToUpload := "precise"
   261  	if seriesToUpload == series.HostSeries() {
   262  		seriesToUpload = "raring"
   263  	}
   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")
   267  }
   268  
   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
   275  	vers.Patch++
   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()})
   279  }
   280  
   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), "")
   288  }
   289  
   290  func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) {
   291  	seriesToUpload := "precise"
   292  	if seriesToUpload == series.HostSeries() {
   293  		seriesToUpload = "raring"
   294  	}
   295  	builtTools, err := sync.BuildToolsTarball(nil, "testing")
   296  	c.Assert(err, jc.ErrorIsNil)
   297  
   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")
   301  }
   302  
   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
   309  	vers.Patch++
   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()})
   315  }
   316  
   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)
   320  
   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))
   331  	}
   332  	metadata, err := envtools.ReadMetadata(s.targetStorage, stream)
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	c.Assert(metadata, gc.HasLen, 0)
   335  }
   336  
   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)
   343  	var buf bytes.Buffer
   344  	_, err = io.Copy(&buf, resp.Body)
   345  	c.Assert(err, jc.ErrorIsNil)
   346  	return buf.Bytes()
   347  }
   348  
   349  func bundleTools(c *gc.C) (version.Binary, string, error) {
   350  	f, err := ioutil.TempFile("", "juju-tgz")
   351  	c.Assert(err, jc.ErrorIsNil)
   352  	defer f.Close()
   353  	defer os.Remove(f.Name())
   354  
   355  	return envtools.BundleTools(f, &jujuversion.Current)
   356  }
   357  
   358  type badBuildSuite struct {
   359  	env environs.Environ
   360  	gitjujutesting.LoggingSuite
   361  	gitjujutesting.CleanupSuite
   362  	envtesting.ToolsFixture
   363  }
   364  
   365  var badGo = `
   366  #!/bin/bash --norc
   367  exit 1
   368  `[1:]
   369  
   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")
   373  	}
   374  	s.CleanupSuite.SetUpSuite(c)
   375  	s.LoggingSuite.SetUpSuite(c)
   376  }
   377  
   378  func (s *badBuildSuite) TearDownSuite(c *gc.C) {
   379  	s.LoggingSuite.TearDownSuite(c)
   380  	s.CleanupSuite.TearDownSuite(c)
   381  }
   382  
   383  func (s *badBuildSuite) SetUpTest(c *gc.C) {
   384  	s.CleanupSuite.SetUpTest(c)
   385  	s.LoggingSuite.SetUpTest(c)
   386  	s.ToolsFixture.SetUpTest(c)
   387  
   388  	// Mock go cmd
   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)
   394  
   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, "")
   399  }
   400  
   401  func (s *badBuildSuite) TearDownTest(c *gc.C) {
   402  	s.ToolsFixture.TearDownTest(c)
   403  	s.LoggingSuite.TearDownTest(c)
   404  	s.CleanupSuite.TearDownTest(c)
   405  }
   406  
   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(),
   412  	}
   413  	c.Assert(v, gc.Equals, current)
   414  }
   415  
   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; `)
   422  
   423  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c))
   424  
   425  	// Test that BundleTools func passes after it is
   426  	// mocked out
   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")
   431  }
   432  
   433  func (s *badBuildSuite) TestUploadToolsBadBuild(c *gc.C) {
   434  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   435  	c.Assert(err, jc.ErrorIsNil)
   436  
   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; `)
   441  
   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), "")
   448  }
   449  
   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)
   455  
   456  	// Test that BuildToolsTarball func passes after BundleTools func is
   457  	// mocked out
   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)
   462  }
   463  
   464  func (s *uploadSuite) TestMockBundleTools(c *gc.C) {
   465  	var (
   466  		writer       io.Writer
   467  		forceVersion *version.Number
   468  		n            int
   469  		p            bytes.Buffer
   470  	)
   471  	p.WriteString("Hello World")
   472  
   473  	s.PatchValue(&envtools.BundleTools, func(writerArg io.Writer, forceVersionArg *version.Number) (vers version.Binary, sha256Hash string, err error) {
   474  		writer = writerArg
   475  		n, err = writer.Write(p.Bytes())
   476  		c.Assert(err, jc.ErrorIsNil)
   477  		forceVersion = forceVersionArg
   478  		return
   479  	})
   480  
   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()))
   486  }
   487  
   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)
   496  
   497  	builtTools.Dir = ""
   498  
   499  	expectedBuiltTools := &sync.BuiltTools{
   500  		StorageName: "name",
   501  		Version:     current,
   502  		Size:        127,
   503  		Sha256Hash:  "6a19d08ca4913382ca86508aa38eb8ee5b9ae2d74333fe8d862c0f9e29b82c39",
   504  	}
   505  	c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools)
   506  
   507  	vers := version.MustParseBinary("1.5.3-trusty-amd64")
   508  	builtTools, err = buildToolsFunc(&vers.Number, "released")
   509  	c.Assert(err, jc.ErrorIsNil)
   510  	builtTools.Dir = ""
   511  	expectedBuiltTools = &sync.BuiltTools{
   512  		StorageName: "name",
   513  		Version:     vers,
   514  		Size:        127,
   515  		Sha256Hash:  "cad8ccedab8f26807ff379ddc2f2f78d9a7cac1276e001154cee5e39b9ddcc38",
   516  	}
   517  	c.Assert(builtTools, gc.DeepEquals, expectedBuiltTools)
   518  }
   519  
   520  func (s *uploadSuite) TestStorageToolsUploaderWriteMirrors(c *gc.C) {
   521  	s.testStorageToolsUploaderWriteMirrors(c, envtools.WriteMirrors)
   522  }
   523  
   524  func (s *uploadSuite) TestStorageToolsUploaderDontWriteMirrors(c *gc.C) {
   525  	s.testStorageToolsUploaderWriteMirrors(c, envtools.DoNotWriteMirrors)
   526  }
   527  
   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)
   532  
   533  	uploader := &sync.StorageToolsUploader{
   534  		Storage:       stor,
   535  		WriteMetadata: true,
   536  		WriteMirrors:  writeMirrors,
   537  	}
   538  
   539  	err = uploader.UploadTools(
   540  		"released",
   541  		"released",
   542  		&coretools.Tools{
   543  			Version: version.Binary{
   544  				Number: jujuversion.Current,
   545  				Arch:   arch.HostArch(),
   546  				Series: series.HostSeries(),
   547  			},
   548  			Size:   7,
   549  			SHA256: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73",
   550  		}, []byte("content"))
   551  	c.Assert(err, jc.ErrorIsNil)
   552  
   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)
   558  		r.Close()
   559  		c.Assert(err, jc.ErrorIsNil)
   560  		c.Assert(string(data), jc.Contains, `"mirrors":`)
   561  	} else {
   562  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
   563  	}
   564  }
   565  
   566  type mockToolsFinder struct{}
   567  
   568  func (mockToolsFinder) FindTools(major int, stream string) (coretools.List, error) {
   569  	return nil, coretools.ErrNoMatches
   570  }