github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/environs/tools/simplestreams_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package tools_test
     5  
     6  import (
     7  	"bytes"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"reflect"
    13  	"strings"
    14  	"testing"
    15  
    16  	"launchpad.net/goamz/aws"
    17  	gc "launchpad.net/gocheck"
    18  
    19  	"launchpad.net/juju-core/environs/filestorage"
    20  	"launchpad.net/juju-core/environs/jujutest"
    21  	"launchpad.net/juju-core/environs/simplestreams"
    22  	sstesting "launchpad.net/juju-core/environs/simplestreams/testing"
    23  	"launchpad.net/juju-core/environs/storage"
    24  	"launchpad.net/juju-core/environs/tools"
    25  	ttesting "launchpad.net/juju-core/environs/tools/testing"
    26  	"launchpad.net/juju-core/testing/testbase"
    27  	coretools "launchpad.net/juju-core/tools"
    28  	"launchpad.net/juju-core/version"
    29  )
    30  
    31  var live = flag.Bool("live", false, "Include live simplestreams tests")
    32  var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data")
    33  
    34  type liveTestData struct {
    35  	baseURL        string
    36  	requireSigned  bool
    37  	validCloudSpec simplestreams.CloudSpec
    38  }
    39  
    40  var liveUrls = map[string]liveTestData{
    41  	"ec2": {
    42  		baseURL:        tools.DefaultBaseURL,
    43  		requireSigned:  true,
    44  		validCloudSpec: simplestreams.CloudSpec{"us-east-1", aws.Regions["us-east-1"].EC2Endpoint},
    45  	},
    46  	"canonistack": {
    47  		baseURL:        "https://swift.canonistack.canonical.com/v1/AUTH_526ad877f3e3464589dc1145dfeaac60/juju-tools",
    48  		requireSigned:  false,
    49  		validCloudSpec: simplestreams.CloudSpec{"lcy01", "https://keystone.canonistack.canonical.com:443/v2.0/"},
    50  	},
    51  }
    52  
    53  func setupSimpleStreamsTests(t *testing.T) {
    54  	if *live {
    55  		if *vendor == "" {
    56  			t.Fatal("missing vendor")
    57  		}
    58  		var ok bool
    59  		var testData liveTestData
    60  		if testData, ok = liveUrls[*vendor]; !ok {
    61  			keys := reflect.ValueOf(liveUrls).MapKeys()
    62  			t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys)
    63  		}
    64  		registerLiveSimpleStreamsTests(testData.baseURL,
    65  			tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
    66  				CloudSpec: testData.validCloudSpec,
    67  				Series:    []string{version.Current.Series},
    68  				Arches:    []string{"amd64"},
    69  			}), testData.requireSigned)
    70  	}
    71  	registerSimpleStreamsTests()
    72  }
    73  
    74  func registerSimpleStreamsTests() {
    75  	gc.Suite(&simplestreamsSuite{
    76  		LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{
    77  			Source:        simplestreams.NewURLDataSource("test", "test:", simplestreams.VerifySSLHostnames),
    78  			RequireSigned: false,
    79  			DataType:      tools.ContentDownload,
    80  			ValidConstraint: tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
    81  				CloudSpec: simplestreams.CloudSpec{
    82  					Region:   "us-east-1",
    83  					Endpoint: "https://ec2.us-east-1.amazonaws.com",
    84  				},
    85  				Series: []string{"precise"},
    86  				Arches: []string{"amd64", "arm"},
    87  			}),
    88  		},
    89  	})
    90  	gc.Suite(&signedSuite{})
    91  }
    92  
    93  func registerLiveSimpleStreamsTests(baseURL string, validToolsConstraint simplestreams.LookupConstraint, requireSigned bool) {
    94  	gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{
    95  		Source:          simplestreams.NewURLDataSource("test", baseURL, simplestreams.VerifySSLHostnames),
    96  		RequireSigned:   requireSigned,
    97  		DataType:        tools.ContentDownload,
    98  		ValidConstraint: validToolsConstraint,
    99  	})
   100  }
   101  
   102  type simplestreamsSuite struct {
   103  	sstesting.LocalLiveSimplestreamsSuite
   104  	sstesting.TestDataSuite
   105  }
   106  
   107  func (s *simplestreamsSuite) SetUpSuite(c *gc.C) {
   108  	s.LocalLiveSimplestreamsSuite.SetUpSuite(c)
   109  	s.TestDataSuite.SetUpSuite(c)
   110  }
   111  
   112  func (s *simplestreamsSuite) TearDownSuite(c *gc.C) {
   113  	s.TestDataSuite.TearDownSuite(c)
   114  	s.LocalLiveSimplestreamsSuite.TearDownSuite(c)
   115  }
   116  
   117  var fetchTests = []struct {
   118  	region   string
   119  	series   string
   120  	version  string
   121  	major    int
   122  	minor    int
   123  	released bool
   124  	arches   []string
   125  	tools    []*tools.ToolsMetadata
   126  }{{
   127  	series:  "precise",
   128  	arches:  []string{"amd64", "arm"},
   129  	version: "1.13.0",
   130  	tools: []*tools.ToolsMetadata{
   131  		{
   132  			Release:  "precise",
   133  			Version:  "1.13.0",
   134  			Arch:     "amd64",
   135  			Size:     2973595,
   136  			Path:     "tools/releases/20130806/juju-1.13.0-precise-amd64.tgz",
   137  			FileType: "tar.gz",
   138  			SHA256:   "447aeb6a934a5eaec4f703eda4ef2dde",
   139  		},
   140  	},
   141  }, {
   142  	series:  "raring",
   143  	arches:  []string{"amd64", "arm"},
   144  	version: "1.13.0",
   145  	tools: []*tools.ToolsMetadata{
   146  		{
   147  			Release:  "raring",
   148  			Version:  "1.13.0",
   149  			Arch:     "amd64",
   150  			Size:     2973173,
   151  			Path:     "tools/releases/20130806/juju-1.13.0-raring-amd64.tgz",
   152  			FileType: "tar.gz",
   153  			SHA256:   "df07ac5e1fb4232d4e9aa2effa57918a",
   154  		},
   155  	},
   156  }, {
   157  	series:  "raring",
   158  	arches:  []string{"amd64", "arm"},
   159  	version: "1.11.4",
   160  	tools: []*tools.ToolsMetadata{
   161  		{
   162  			Release:  "raring",
   163  			Version:  "1.11.4",
   164  			Arch:     "arm",
   165  			Size:     1950327,
   166  			Path:     "tools/releases/20130806/juju-1.11.4-raring-arm.tgz",
   167  			FileType: "tar.gz",
   168  			SHA256:   "6472014e3255e3fe7fbd3550ef3f0a11",
   169  		},
   170  	},
   171  }, {
   172  	series: "precise",
   173  	arches: []string{"amd64", "arm"},
   174  	major:  2,
   175  	tools: []*tools.ToolsMetadata{
   176  		{
   177  			Release:  "precise",
   178  			Version:  "2.0.1",
   179  			Arch:     "arm",
   180  			Size:     1951096,
   181  			Path:     "tools/releases/20130806/juju-2.0.1-precise-arm.tgz",
   182  			FileType: "tar.gz",
   183  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   184  		},
   185  	},
   186  }, {
   187  	series: "precise",
   188  	arches: []string{"amd64", "arm"},
   189  	major:  1,
   190  	minor:  11,
   191  	tools: []*tools.ToolsMetadata{
   192  		{
   193  			Release:  "precise",
   194  			Version:  "1.11.4",
   195  			Arch:     "arm",
   196  			Size:     1951096,
   197  			Path:     "tools/releases/20130806/juju-1.11.4-precise-arm.tgz",
   198  			FileType: "tar.gz",
   199  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   200  		},
   201  		{
   202  			Release:  "precise",
   203  			Version:  "1.11.5",
   204  			Arch:     "arm",
   205  			Size:     2031281,
   206  			Path:     "tools/releases/20130803/juju-1.11.5-precise-arm.tgz",
   207  			FileType: "tar.gz",
   208  			SHA256:   "df07ac5e1fb4232d4e9aa2effa57918a",
   209  		},
   210  	},
   211  }, {
   212  	series:   "raring",
   213  	arches:   []string{"amd64", "arm"},
   214  	major:    1,
   215  	minor:    -1,
   216  	released: true,
   217  	tools: []*tools.ToolsMetadata{
   218  		{
   219  			Release:  "raring",
   220  			Version:  "1.14.0",
   221  			Arch:     "amd64",
   222  			Size:     2973173,
   223  			Path:     "tools/releases/20130806/juju-1.14.0-raring-amd64.tgz",
   224  			FileType: "tar.gz",
   225  			SHA256:   "df07ac5e1fb4232d4e9aa2effa57918a",
   226  		},
   227  	},
   228  }}
   229  
   230  func (s *simplestreamsSuite) TestFetch(c *gc.C) {
   231  	for i, t := range fetchTests {
   232  		c.Logf("test %d", i)
   233  		var toolsConstraint *tools.ToolsConstraint
   234  		if t.version == "" {
   235  			toolsConstraint = tools.NewGeneralToolsConstraint(t.major, t.minor, t.released, simplestreams.LookupParams{
   236  				CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   237  				Series:    []string{t.series},
   238  				Arches:    t.arches,
   239  			})
   240  		} else {
   241  			toolsConstraint = tools.NewVersionedToolsConstraint(version.MustParse(t.version),
   242  				simplestreams.LookupParams{
   243  					CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   244  					Series:    []string{t.series},
   245  					Arches:    t.arches,
   246  				})
   247  		}
   248  		// Add invalid datasource and check later that resolveInfo is correct.
   249  		invalidSource := simplestreams.NewURLDataSource("invalid", "file://invalid", simplestreams.VerifySSLHostnames)
   250  		tools, resolveInfo, err := tools.Fetch(
   251  			[]simplestreams.DataSource{invalidSource, s.Source},
   252  			simplestreams.DefaultIndexPath, toolsConstraint, s.RequireSigned)
   253  		if !c.Check(err, gc.IsNil) {
   254  			continue
   255  		}
   256  		for _, tm := range t.tools {
   257  			tm.FullPath, err = s.Source.URL(tm.Path)
   258  			c.Assert(err, gc.IsNil)
   259  		}
   260  		c.Check(tools, gc.DeepEquals, t.tools)
   261  		c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   262  			Source:    "test",
   263  			Signed:    s.RequireSigned,
   264  			IndexURL:  "test:/streams/v1/index.json",
   265  			MirrorURL: "",
   266  		})
   267  	}
   268  }
   269  
   270  func (s *simplestreamsSuite) TestFetchWithMirror(c *gc.C) {
   271  	toolsConstraint := tools.NewGeneralToolsConstraint(1, 13, false, simplestreams.LookupParams{
   272  		CloudSpec: simplestreams.CloudSpec{"us-west-2", "https://ec2.us-west-2.amazonaws.com"},
   273  		Series:    []string{"precise"},
   274  		Arches:    []string{"amd64"},
   275  	})
   276  	toolsMetadata, resolveInfo, err := tools.Fetch(
   277  		[]simplestreams.DataSource{s.Source}, simplestreams.DefaultIndexPath, toolsConstraint, s.RequireSigned)
   278  	c.Assert(err, gc.IsNil)
   279  	c.Assert(len(toolsMetadata), gc.Equals, 1)
   280  
   281  	expectedMetadata := &tools.ToolsMetadata{
   282  		Release:  "precise",
   283  		Version:  "1.13.0",
   284  		Arch:     "amd64",
   285  		Size:     2973595,
   286  		Path:     "mirrored-path/juju-1.13.0-precise-amd64.tgz",
   287  		FullPath: "test:/mirrored-path/juju-1.13.0-precise-amd64.tgz",
   288  		FileType: "tar.gz",
   289  		SHA256:   "447aeb6a934a5eaec4f703eda4ef2dde",
   290  	}
   291  	c.Assert(err, gc.IsNil)
   292  	c.Assert(toolsMetadata[0], gc.DeepEquals, expectedMetadata)
   293  	c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   294  		Source:    "test",
   295  		Signed:    s.RequireSigned,
   296  		IndexURL:  "test:/streams/v1/index.json",
   297  		MirrorURL: "test:/",
   298  	})
   299  }
   300  
   301  func assertMetadataMatches(c *gc.C, storageDir string, toolList coretools.List, metadata []*tools.ToolsMetadata) {
   302  	var expectedMetadata []*tools.ToolsMetadata = make([]*tools.ToolsMetadata, len(toolList))
   303  	for i, tool := range toolList {
   304  		expectedMetadata[i] = &tools.ToolsMetadata{
   305  			Release:  tool.Version.Series,
   306  			Version:  tool.Version.Number.String(),
   307  			Arch:     tool.Version.Arch,
   308  			Size:     tool.Size,
   309  			Path:     fmt.Sprintf("releases/juju-%s.tgz", tool.Version.String()),
   310  			FileType: "tar.gz",
   311  			SHA256:   tool.SHA256,
   312  		}
   313  	}
   314  	c.Assert(metadata, gc.DeepEquals, expectedMetadata)
   315  }
   316  
   317  func (s *simplestreamsSuite) TestWriteMetadataNoFetch(c *gc.C) {
   318  	toolsList := coretools.List{
   319  		{
   320  			Version: version.MustParseBinary("1.2.3-precise-amd64"),
   321  			Size:    123,
   322  			SHA256:  "abcd",
   323  		}, {
   324  			Version: version.MustParseBinary("2.0.1-raring-amd64"),
   325  			Size:    456,
   326  			SHA256:  "xyz",
   327  		},
   328  	}
   329  	dir := c.MkDir()
   330  	writer, err := filestorage.NewFileStorageWriter(dir)
   331  	c.Assert(err, gc.IsNil)
   332  	err = tools.MergeAndWriteMetadata(writer, toolsList, tools.DoNotWriteMirrors)
   333  	c.Assert(err, gc.IsNil)
   334  	metadata := ttesting.ParseMetadataFromDir(c, dir, false)
   335  	assertMetadataMatches(c, dir, toolsList, metadata)
   336  }
   337  
   338  func (s *simplestreamsSuite) assertWriteMetadata(c *gc.C, withMirrors bool) {
   339  	var versionStrings = []string{
   340  		"1.2.3-precise-amd64",
   341  		"2.0.1-raring-amd64",
   342  	}
   343  	dir := c.MkDir()
   344  	ttesting.MakeTools(c, dir, "releases", versionStrings)
   345  
   346  	toolsList := coretools.List{
   347  		{
   348  			// If sha256/size is already known, do not recalculate
   349  			Version: version.MustParseBinary("1.2.3-precise-amd64"),
   350  			Size:    123,
   351  			SHA256:  "abcd",
   352  		}, {
   353  			Version: version.MustParseBinary("2.0.1-raring-amd64"),
   354  			// The URL is not used for generating metadata.
   355  			URL: "bogus://",
   356  		},
   357  	}
   358  	writer, err := filestorage.NewFileStorageWriter(dir)
   359  	c.Assert(err, gc.IsNil)
   360  	writeMirrors := tools.DoNotWriteMirrors
   361  	if withMirrors {
   362  		writeMirrors = tools.WriteMirrors
   363  	}
   364  	err = tools.MergeAndWriteMetadata(writer, toolsList, writeMirrors)
   365  	c.Assert(err, gc.IsNil)
   366  	metadata := ttesting.ParseMetadataFromDir(c, dir, withMirrors)
   367  	assertMetadataMatches(c, dir, toolsList, metadata)
   368  }
   369  
   370  func (s *simplestreamsSuite) TestWriteMetadata(c *gc.C) {
   371  	s.assertWriteMetadata(c, false)
   372  }
   373  
   374  func (s *simplestreamsSuite) TestWriteMetadataWithMirrors(c *gc.C) {
   375  	s.assertWriteMetadata(c, true)
   376  }
   377  
   378  func (s *simplestreamsSuite) TestWriteMetadataMergeWithExisting(c *gc.C) {
   379  	dir := c.MkDir()
   380  	existingToolsList := coretools.List{
   381  		{
   382  			Version: version.MustParseBinary("1.2.3-precise-amd64"),
   383  			Size:    123,
   384  			SHA256:  "abc",
   385  		}, {
   386  			Version: version.MustParseBinary("2.0.1-raring-amd64"),
   387  			Size:    456,
   388  			SHA256:  "xyz",
   389  		},
   390  	}
   391  	writer, err := filestorage.NewFileStorageWriter(dir)
   392  	c.Assert(err, gc.IsNil)
   393  	err = tools.MergeAndWriteMetadata(writer, existingToolsList, tools.DoNotWriteMirrors)
   394  	c.Assert(err, gc.IsNil)
   395  	newToolsList := coretools.List{
   396  		existingToolsList[0],
   397  		{
   398  			Version: version.MustParseBinary("2.1.0-raring-amd64"),
   399  			Size:    789,
   400  			SHA256:  "def",
   401  		},
   402  	}
   403  	err = tools.MergeAndWriteMetadata(writer, newToolsList, tools.DoNotWriteMirrors)
   404  	c.Assert(err, gc.IsNil)
   405  	requiredToolsList := append(existingToolsList, newToolsList[1])
   406  	metadata := ttesting.ParseMetadataFromDir(c, dir, false)
   407  	assertMetadataMatches(c, dir, requiredToolsList, metadata)
   408  }
   409  
   410  type productSpecSuite struct{}
   411  
   412  var _ = gc.Suite(&productSpecSuite{})
   413  
   414  func (s *productSpecSuite) TestId(c *gc.C) {
   415  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   416  		Series: []string{"precise"},
   417  		Arches: []string{"amd64"},
   418  	})
   419  	ids, err := toolsConstraint.Ids()
   420  	c.Assert(err, gc.IsNil)
   421  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:12.04:amd64"})
   422  }
   423  
   424  func (s *productSpecSuite) TestIdMultiArch(c *gc.C) {
   425  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   426  		Series: []string{"precise"},
   427  		Arches: []string{"amd64", "arm"},
   428  	})
   429  	ids, err := toolsConstraint.Ids()
   430  	c.Assert(err, gc.IsNil)
   431  	c.Assert(ids, gc.DeepEquals, []string{
   432  		"com.ubuntu.juju:12.04:amd64",
   433  		"com.ubuntu.juju:12.04:arm"})
   434  }
   435  
   436  func (s *productSpecSuite) TestIdMultiSeries(c *gc.C) {
   437  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   438  		Series: []string{"precise", "raring"},
   439  		Arches: []string{"amd64"},
   440  	})
   441  	ids, err := toolsConstraint.Ids()
   442  	c.Assert(err, gc.IsNil)
   443  	c.Assert(ids, gc.DeepEquals, []string{
   444  		"com.ubuntu.juju:12.04:amd64",
   445  		"com.ubuntu.juju:13.04:amd64"})
   446  }
   447  
   448  func (s *productSpecSuite) TestIdWithMajorVersionOnly(c *gc.C) {
   449  	toolsConstraint := tools.NewGeneralToolsConstraint(1, -1, false, simplestreams.LookupParams{
   450  		Series: []string{"precise"},
   451  		Arches: []string{"amd64"},
   452  	})
   453  	ids, err := toolsConstraint.Ids()
   454  	c.Assert(err, gc.IsNil)
   455  	c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`})
   456  }
   457  
   458  func (s *productSpecSuite) TestIdWithMajorMinorVersion(c *gc.C) {
   459  	toolsConstraint := tools.NewGeneralToolsConstraint(1, 2, false, simplestreams.LookupParams{
   460  		Series: []string{"precise"},
   461  		Arches: []string{"amd64"},
   462  	})
   463  	ids, err := toolsConstraint.Ids()
   464  	c.Assert(err, gc.IsNil)
   465  	c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`})
   466  }
   467  
   468  func (s *productSpecSuite) TestLargeNumber(c *gc.C) {
   469  	json := `{
   470          "updated": "Fri, 30 Aug 2013 16:12:58 +0800",
   471          "format": "products:1.0",
   472          "products": {
   473              "com.ubuntu.juju:1.10.0:amd64": {
   474                  "version": "1.10.0",
   475                  "arch": "amd64",
   476                  "versions": {
   477                      "20133008": {
   478                          "items": {
   479                              "1.10.0-precise-amd64": {
   480                                  "release": "precise",
   481                                  "version": "1.10.0",
   482                                  "arch": "amd64",
   483                                  "size": 9223372036854775807,
   484                                  "path": "releases/juju-1.10.0-precise-amd64.tgz",
   485                                  "ftype": "tar.gz",
   486                                  "sha256": ""
   487                              }
   488                          }
   489                      }
   490                  }
   491              }
   492          }
   493      }`
   494  	cloudMetadata, err := simplestreams.ParseCloudMetadata([]byte(json), "products:1.0", "", tools.ToolsMetadata{})
   495  	c.Assert(err, gc.IsNil)
   496  	c.Assert(cloudMetadata.Products, gc.HasLen, 1)
   497  	product := cloudMetadata.Products["com.ubuntu.juju:1.10.0:amd64"]
   498  	c.Assert(product, gc.NotNil)
   499  	c.Assert(product.Items, gc.HasLen, 1)
   500  	version := product.Items["20133008"]
   501  	c.Assert(version, gc.NotNil)
   502  	c.Assert(version.Items, gc.HasLen, 1)
   503  	item := version.Items["1.10.0-precise-amd64"]
   504  	c.Assert(item, gc.NotNil)
   505  	c.Assert(item, gc.FitsTypeOf, &tools.ToolsMetadata{})
   506  	c.Assert(item.(*tools.ToolsMetadata).Size, gc.Equals, int64(9223372036854775807))
   507  }
   508  
   509  type metadataHelperSuite struct {
   510  	testbase.LoggingSuite
   511  }
   512  
   513  var _ = gc.Suite(&metadataHelperSuite{})
   514  
   515  func (*metadataHelperSuite) TestMetadataFromTools(c *gc.C) {
   516  	metadata := tools.MetadataFromTools(nil)
   517  	c.Assert(metadata, gc.HasLen, 0)
   518  
   519  	toolsList := coretools.List{{
   520  		Version: version.MustParseBinary("1.2.3-precise-amd64"),
   521  		Size:    123,
   522  		SHA256:  "abc",
   523  	}, {
   524  		Version: version.MustParseBinary("2.0.1-raring-amd64"),
   525  		URL:     "file:///tmp/releases/juju-2.0.1-raring-amd64.tgz",
   526  		Size:    456,
   527  		SHA256:  "xyz",
   528  	}}
   529  	metadata = tools.MetadataFromTools(toolsList)
   530  	c.Assert(metadata, gc.HasLen, len(toolsList))
   531  	for i, t := range toolsList {
   532  		md := metadata[i]
   533  		c.Assert(md.Release, gc.Equals, t.Version.Series)
   534  		c.Assert(md.Version, gc.Equals, t.Version.Number.String())
   535  		c.Assert(md.Arch, gc.Equals, t.Version.Arch)
   536  		// FullPath is only filled out when reading tools using simplestreams.
   537  		// It's not needed elsewhere and requires a URL() call.
   538  		c.Assert(md.FullPath, gc.Equals, "")
   539  		c.Assert(md.Path, gc.Equals, tools.StorageName(t.Version)[len("tools/"):])
   540  		c.Assert(md.FileType, gc.Equals, "tar.gz")
   541  		c.Assert(md.Size, gc.Equals, t.Size)
   542  		c.Assert(md.SHA256, gc.Equals, t.SHA256)
   543  	}
   544  }
   545  
   546  type countingStorage struct {
   547  	storage.StorageReader
   548  	counter int
   549  }
   550  
   551  func (c *countingStorage) Get(name string) (io.ReadCloser, error) {
   552  	c.counter++
   553  	return c.StorageReader.Get(name)
   554  }
   555  
   556  func (*metadataHelperSuite) TestResolveMetadata(c *gc.C) {
   557  	var versionStrings = []string{"1.2.3-precise-amd64"}
   558  	dir := c.MkDir()
   559  	ttesting.MakeTools(c, dir, "releases", versionStrings)
   560  	toolsList := coretools.List{{
   561  		Version: version.MustParseBinary(versionStrings[0]),
   562  		Size:    123,
   563  		SHA256:  "abc",
   564  	}}
   565  
   566  	stor, err := filestorage.NewFileStorageReader(dir)
   567  	c.Assert(err, gc.IsNil)
   568  	err = tools.ResolveMetadata(stor, nil)
   569  	c.Assert(err, gc.IsNil)
   570  
   571  	// We already have size/sha256, so ensure that storage isn't consulted.
   572  	countingStorage := &countingStorage{StorageReader: stor}
   573  	metadata := tools.MetadataFromTools(toolsList)
   574  	err = tools.ResolveMetadata(countingStorage, metadata)
   575  	c.Assert(err, gc.IsNil)
   576  	c.Assert(countingStorage.counter, gc.Equals, 0)
   577  
   578  	// Now clear size/sha256, and check that it is called, and
   579  	// the size/sha256 sum are updated.
   580  	metadata[0].Size = 0
   581  	metadata[0].SHA256 = ""
   582  	err = tools.ResolveMetadata(countingStorage, metadata)
   583  	c.Assert(err, gc.IsNil)
   584  	c.Assert(countingStorage.counter, gc.Equals, 1)
   585  	c.Assert(metadata[0].Size, gc.Not(gc.Equals), 0)
   586  	c.Assert(metadata[0].SHA256, gc.Not(gc.Equals), "")
   587  }
   588  
   589  func (*metadataHelperSuite) TestMergeMetadata(c *gc.C) {
   590  	md1 := &tools.ToolsMetadata{
   591  		Release: "precise",
   592  		Version: "1.2.3",
   593  		Arch:    "amd64",
   594  		Path:    "path1",
   595  	}
   596  	md2 := &tools.ToolsMetadata{
   597  		Release: "precise",
   598  		Version: "1.2.3",
   599  		Arch:    "amd64",
   600  		Path:    "path2",
   601  	}
   602  	md3 := &tools.ToolsMetadata{
   603  		Release: "raring",
   604  		Version: "1.2.3",
   605  		Arch:    "amd64",
   606  		Path:    "path3",
   607  	}
   608  
   609  	withSize := func(md *tools.ToolsMetadata, size int64) *tools.ToolsMetadata {
   610  		clone := *md
   611  		clone.Size = size
   612  		return &clone
   613  	}
   614  	withSHA256 := func(md *tools.ToolsMetadata, sha256 string) *tools.ToolsMetadata {
   615  		clone := *md
   616  		clone.SHA256 = sha256
   617  		return &clone
   618  	}
   619  
   620  	type mdlist []*tools.ToolsMetadata
   621  	type test struct {
   622  		name             string
   623  		lhs, rhs, merged []*tools.ToolsMetadata
   624  		err              string
   625  	}
   626  	tests := []test{{
   627  		name:   "non-empty lhs, empty rhs",
   628  		lhs:    mdlist{md1},
   629  		rhs:    nil,
   630  		merged: mdlist{md1},
   631  	}, {
   632  		name:   "empty lhs, non-empty rhs",
   633  		lhs:    nil,
   634  		rhs:    mdlist{md2},
   635  		merged: mdlist{md2},
   636  	}, {
   637  		name:   "identical lhs, rhs",
   638  		lhs:    mdlist{md1},
   639  		rhs:    mdlist{md1},
   640  		merged: mdlist{md1},
   641  	}, {
   642  		name:   "same tools in lhs and rhs, neither have size: prefer lhs",
   643  		lhs:    mdlist{md1},
   644  		rhs:    mdlist{md2},
   645  		merged: mdlist{md1},
   646  	}, {
   647  		name:   "same tools in lhs and rhs, only lhs has a size: prefer lhs",
   648  		lhs:    mdlist{withSize(md1, 123)},
   649  		rhs:    mdlist{md2},
   650  		merged: mdlist{withSize(md1, 123)},
   651  	}, {
   652  		name:   "same tools in lhs and rhs, only rhs has a size: prefer rhs",
   653  		lhs:    mdlist{md1},
   654  		rhs:    mdlist{withSize(md2, 123)},
   655  		merged: mdlist{withSize(md2, 123)},
   656  	}, {
   657  		name:   "same tools in lhs and rhs, both have the same size: prefer lhs",
   658  		lhs:    mdlist{withSize(md1, 123)},
   659  		rhs:    mdlist{withSize(md2, 123)},
   660  		merged: mdlist{withSize(md1, 123)},
   661  	}, {
   662  		name: "same tools in lhs and rhs, both have different sizes: error",
   663  		lhs:  mdlist{withSize(md1, 123)},
   664  		rhs:  mdlist{withSize(md2, 456)},
   665  		err:  "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,456\\) sha256=\\(,\\)",
   666  	}, {
   667  		name: "same tools in lhs and rhs, both have same size but different sha256: error",
   668  		lhs:  mdlist{withSHA256(withSize(md1, 123), "a")},
   669  		rhs:  mdlist{withSHA256(withSize(md2, 123), "b")},
   670  		err:  "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,123\\) sha256=\\(a,b\\)",
   671  	}, {
   672  		name:   "lhs is a proper superset of rhs: union of lhs and rhs",
   673  		lhs:    mdlist{md1, md3},
   674  		rhs:    mdlist{md1},
   675  		merged: mdlist{md1, md3},
   676  	}, {
   677  		name:   "rhs is a proper superset of lhs: union of lhs and rhs",
   678  		lhs:    mdlist{md1},
   679  		rhs:    mdlist{md1, md3},
   680  		merged: mdlist{md1, md3},
   681  	}}
   682  	for i, test := range tests {
   683  		c.Logf("test %d: %s", i, test.name)
   684  		merged, err := tools.MergeMetadata(test.lhs, test.rhs)
   685  		if test.err == "" {
   686  			c.Assert(err, gc.IsNil)
   687  			c.Assert(merged, gc.DeepEquals, test.merged)
   688  		} else {
   689  			c.Assert(err, gc.ErrorMatches, test.err)
   690  			c.Assert(merged, gc.IsNil)
   691  		}
   692  	}
   693  }
   694  
   695  func (*metadataHelperSuite) TestReadWriteMetadata(c *gc.C) {
   696  	metadata := []*tools.ToolsMetadata{{
   697  		Release: "precise",
   698  		Version: "1.2.3",
   699  		Arch:    "amd64",
   700  		Path:    "path1",
   701  	}, {
   702  		Release: "raring",
   703  		Version: "1.2.3",
   704  		Arch:    "amd64",
   705  		Path:    "path2",
   706  	}}
   707  
   708  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   709  	c.Assert(err, gc.IsNil)
   710  	out, err := tools.ReadMetadata(stor)
   711  	c.Assert(out, gc.HasLen, 0)
   712  	c.Assert(err, gc.IsNil) // non-existence is not an error
   713  	err = tools.WriteMetadata(stor, metadata, tools.DoNotWriteMirrors)
   714  	c.Assert(err, gc.IsNil)
   715  	out, err = tools.ReadMetadata(stor)
   716  	for _, md := range out {
   717  		// FullPath is set by ReadMetadata.
   718  		c.Assert(md.FullPath, gc.Not(gc.Equals), "")
   719  		md.FullPath = ""
   720  	}
   721  	c.Assert(out, gc.DeepEquals, metadata)
   722  }
   723  
   724  type signedSuite struct {
   725  	origKey string
   726  }
   727  
   728  var testRoundTripper *jujutest.ProxyRoundTripper
   729  
   730  func init() {
   731  	testRoundTripper = &jujutest.ProxyRoundTripper{}
   732  	simplestreams.RegisterProtocol("signedtest", testRoundTripper)
   733  }
   734  
   735  func (s *signedSuite) SetUpSuite(c *gc.C) {
   736  	var imageData = map[string]string{
   737  		"/unsigned/streams/v1/index.json":          unsignedIndex,
   738  		"/unsigned/streams/v1/tools_metadata.json": unsignedProduct,
   739  	}
   740  
   741  	// Set up some signed data from the unsigned data.
   742  	// Overwrite the product path to use the sjson suffix.
   743  	rawUnsignedIndex := strings.Replace(
   744  		unsignedIndex, "streams/v1/tools_metadata.json", "streams/v1/tools_metadata.sjson", -1)
   745  	r := bytes.NewReader([]byte(rawUnsignedIndex))
   746  	signedData, err := simplestreams.Encode(
   747  		r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
   748  	c.Assert(err, gc.IsNil)
   749  	imageData["/signed/streams/v1/index.sjson"] = string(signedData)
   750  
   751  	// Replace the tools path in the unsigned data with a different one so we can test that the right
   752  	// tools path is used.
   753  	rawUnsignedProduct := strings.Replace(
   754  		unsignedProduct, "juju-1.13.0", "juju-1.13.1", -1)
   755  	r = bytes.NewReader([]byte(rawUnsignedProduct))
   756  	signedData, err = simplestreams.Encode(
   757  		r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
   758  	c.Assert(err, gc.IsNil)
   759  	imageData["/signed/streams/v1/tools_metadata.sjson"] = string(signedData)
   760  	testRoundTripper.Sub = jujutest.NewCannedRoundTripper(
   761  		imageData, map[string]int{"signedtest://unauth": http.StatusUnauthorized})
   762  	s.origKey = tools.SetSigningPublicKey(sstesting.SignedMetadataPublicKey)
   763  }
   764  
   765  func (s *signedSuite) TearDownSuite(c *gc.C) {
   766  	testRoundTripper.Sub = nil
   767  	tools.SetSigningPublicKey(s.origKey)
   768  }
   769  
   770  func (s *signedSuite) TestSignedToolsMetadata(c *gc.C) {
   771  	signedSource := simplestreams.NewURLDataSource("test", "signedtest://host/signed", simplestreams.VerifySSLHostnames)
   772  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   773  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   774  		Series:    []string{"precise"},
   775  		Arches:    []string{"amd64"},
   776  	})
   777  	toolsMetadata, resolveInfo, err := tools.Fetch(
   778  		[]simplestreams.DataSource{signedSource}, simplestreams.DefaultIndexPath, toolsConstraint, true)
   779  	c.Assert(err, gc.IsNil)
   780  	c.Assert(len(toolsMetadata), gc.Equals, 1)
   781  	c.Assert(toolsMetadata[0].Path, gc.Equals, "tools/releases/20130806/juju-1.13.1-precise-amd64.tgz")
   782  	c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   783  		Source:    "test",
   784  		Signed:    true,
   785  		IndexURL:  "signedtest://host/signed/streams/v1/index.sjson",
   786  		MirrorURL: "",
   787  	})
   788  }
   789  
   790  var unsignedIndex = `
   791  {
   792   "index": {
   793    "com.ubuntu.juju:released:tools": {
   794     "updated": "Mon, 05 Aug 2013 11:07:04 +0000",
   795     "datatype": "content-download",
   796     "format": "products:1.0",
   797     "products": [
   798       "com.ubuntu.juju:12.04:amd64"
   799     ],
   800     "path": "streams/v1/tools_metadata.json"
   801    }
   802   },
   803   "updated": "Wed, 01 May 2013 13:31:26 +0000",
   804   "format": "index:1.0"
   805  }
   806  `
   807  var unsignedProduct = `
   808  {
   809   "updated": "Wed, 01 May 2013 13:31:26 +0000",
   810   "content_id": "com.ubuntu.cloud:released:aws",
   811   "datatype": "content-download",
   812   "products": {
   813     "com.ubuntu.juju:12.04:amd64": {
   814      "arch": "amd64",
   815      "release": "precise",
   816      "versions": {
   817       "20130806": {
   818        "items": {
   819         "1130preciseamd64": {
   820          "version": "1.13.0",
   821          "size": 2973595,
   822          "path": "tools/releases/20130806/juju-1.13.0-precise-amd64.tgz",
   823          "ftype": "tar.gz",
   824          "sha256": "447aeb6a934a5eaec4f703eda4ef2dde"
   825         }
   826        }
   827       }
   828      }
   829     }
   830   },
   831   "format": "products:1.0"
   832  }
   833  `