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