github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"encoding/json"
     9  	"flag"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"os"
    15  	"path/filepath"
    16  	"reflect"
    17  	"runtime"
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/juju/os/series"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/utils"
    24  	"github.com/juju/version"
    25  	"gopkg.in/amz.v3/aws"
    26  	gc "gopkg.in/check.v1"
    27  
    28  	"github.com/juju/juju/environs/filestorage"
    29  	"github.com/juju/juju/environs/simplestreams"
    30  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    31  	"github.com/juju/juju/environs/storage"
    32  	"github.com/juju/juju/environs/tools"
    33  	toolstesting "github.com/juju/juju/environs/tools/testing"
    34  	"github.com/juju/juju/juju/keys"
    35  	coretesting "github.com/juju/juju/testing"
    36  	coretools "github.com/juju/juju/tools"
    37  )
    38  
    39  var live = flag.Bool("live", false, "Include live simplestreams tests")
    40  var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data")
    41  
    42  type liveTestData struct {
    43  	baseURL        string
    44  	requireSigned  bool
    45  	validCloudSpec simplestreams.CloudSpec
    46  }
    47  
    48  var liveURLs = map[string]liveTestData{
    49  	"ec2": {
    50  		baseURL:        tools.DefaultBaseURL,
    51  		requireSigned:  true,
    52  		validCloudSpec: simplestreams.CloudSpec{"us-east-1", aws.Regions["us-east-1"].EC2Endpoint},
    53  	},
    54  	"canonistack": {
    55  		baseURL:        "https://swift.canonistack.canonical.com/v1/AUTH_526ad877f3e3464589dc1145dfeaac60/juju-tools",
    56  		requireSigned:  false,
    57  		validCloudSpec: simplestreams.CloudSpec{"lcy01", "https://keystone.canonistack.canonical.com:443/v1.0/"},
    58  	},
    59  }
    60  
    61  func setupSimpleStreamsTests(t *testing.T) {
    62  	if *live {
    63  		if *vendor == "" {
    64  			t.Fatal("missing vendor")
    65  		}
    66  		var ok bool
    67  		var testData liveTestData
    68  		if testData, ok = liveURLs[*vendor]; !ok {
    69  			keys := reflect.ValueOf(liveURLs).MapKeys()
    70  			t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys)
    71  		}
    72  		registerLiveSimpleStreamsTests(testData.baseURL,
    73  			tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
    74  				CloudSpec: testData.validCloudSpec,
    75  				Series:    []string{series.MustHostSeries()},
    76  				Arches:    []string{"amd64"},
    77  				Stream:    "released",
    78  			}), testData.requireSigned)
    79  	}
    80  	registerSimpleStreamsTests()
    81  }
    82  
    83  func registerSimpleStreamsTests() {
    84  	gc.Suite(&simplestreamsSuite{
    85  		LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{
    86  			Source:         simplestreams.NewURLDataSource("test", "test:", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, false),
    87  			RequireSigned:  false,
    88  			DataType:       tools.ContentDownload,
    89  			StreamsVersion: tools.CurrentStreamsVersion,
    90  			ValidConstraint: tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
    91  				CloudSpec: simplestreams.CloudSpec{
    92  					Region:   "us-east-1",
    93  					Endpoint: "https://ec2.us-east-1.amazonaws.com",
    94  				},
    95  				Series: []string{"precise"},
    96  				Arches: []string{"amd64", "arm"},
    97  				Stream: "released",
    98  			}),
    99  		},
   100  	})
   101  	gc.Suite(&signedSuite{})
   102  }
   103  
   104  func registerLiveSimpleStreamsTests(baseURL string, validToolsConstraint simplestreams.LookupConstraint, requireSigned bool) {
   105  	gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{
   106  		Source:          simplestreams.NewURLDataSource("test", baseURL, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, requireSigned),
   107  		RequireSigned:   requireSigned,
   108  		DataType:        tools.ContentDownload,
   109  		StreamsVersion:  tools.CurrentStreamsVersion,
   110  		ValidConstraint: validToolsConstraint,
   111  	})
   112  }
   113  
   114  type simplestreamsSuite struct {
   115  	sstesting.LocalLiveSimplestreamsSuite
   116  	sstesting.TestDataSuite
   117  }
   118  
   119  func (s *simplestreamsSuite) SetUpSuite(c *gc.C) {
   120  	s.LocalLiveSimplestreamsSuite.SetUpSuite(c)
   121  	s.TestDataSuite.SetUpSuite(c)
   122  }
   123  
   124  func (s *simplestreamsSuite) TearDownSuite(c *gc.C) {
   125  	s.TestDataSuite.TearDownSuite(c)
   126  	s.LocalLiveSimplestreamsSuite.TearDownSuite(c)
   127  }
   128  
   129  var fetchTests = []struct {
   130  	region  string
   131  	series  string
   132  	version string
   133  	stream  string
   134  	major   int
   135  	minor   int
   136  	arches  []string
   137  	tools   []*tools.ToolsMetadata
   138  }{{
   139  	series:  "precise",
   140  	arches:  []string{"amd64", "arm"},
   141  	version: "1.13.0",
   142  	tools: []*tools.ToolsMetadata{
   143  		{
   144  			Release:  "precise",
   145  			Version:  "1.13.0",
   146  			Arch:     "amd64",
   147  			Size:     2973595,
   148  			Path:     "tools/released/20130806/juju-1.13.0-precise-amd64.tgz",
   149  			FileType: "tar.gz",
   150  			SHA256:   "447aeb6a934a5eaec4f703eda4ef2dde",
   151  		},
   152  	},
   153  }, {
   154  	series:  "raring",
   155  	arches:  []string{"amd64", "arm"},
   156  	version: "1.13.0",
   157  	tools: []*tools.ToolsMetadata{
   158  		{
   159  			Release:  "raring",
   160  			Version:  "1.13.0",
   161  			Arch:     "amd64",
   162  			Size:     2973173,
   163  			Path:     "tools/released/20130806/juju-1.13.0-raring-amd64.tgz",
   164  			FileType: "tar.gz",
   165  			SHA256:   "df07ac5e1fb4232d4e9aa2effa57918a",
   166  		},
   167  	},
   168  }, {
   169  	series:  "raring",
   170  	arches:  []string{"amd64", "arm"},
   171  	version: "1.11.4",
   172  	tools: []*tools.ToolsMetadata{
   173  		{
   174  			Release:  "raring",
   175  			Version:  "1.11.4",
   176  			Arch:     "arm",
   177  			Size:     1950327,
   178  			Path:     "tools/released/20130806/juju-1.11.4-raring-arm.tgz",
   179  			FileType: "tar.gz",
   180  			SHA256:   "6472014e3255e3fe7fbd3550ef3f0a11",
   181  		},
   182  	},
   183  }, {
   184  	series: "precise",
   185  	arches: []string{"amd64", "arm"},
   186  	major:  2,
   187  	tools: []*tools.ToolsMetadata{
   188  		{
   189  			Release:  "precise",
   190  			Version:  "2.0.1",
   191  			Arch:     "arm",
   192  			Size:     1951096,
   193  			Path:     "tools/released/20130806/juju-2.0.1-precise-arm.tgz",
   194  			FileType: "tar.gz",
   195  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   196  		},
   197  	},
   198  }, {
   199  	series: "precise",
   200  	arches: []string{"amd64", "arm"},
   201  	major:  1,
   202  	minor:  11,
   203  	tools: []*tools.ToolsMetadata{
   204  		{
   205  			Release:  "precise",
   206  			Version:  "1.11.4",
   207  			Arch:     "arm",
   208  			Size:     1951096,
   209  			Path:     "tools/released/20130806/juju-1.11.4-precise-arm.tgz",
   210  			FileType: "tar.gz",
   211  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   212  		},
   213  		{
   214  			Release:  "precise",
   215  			Version:  "1.11.5",
   216  			Arch:     "arm",
   217  			Size:     2031281,
   218  			Path:     "tools/released/20130803/juju-1.11.5-precise-arm.tgz",
   219  			FileType: "tar.gz",
   220  			SHA256:   "df07ac5e1fb4232d4e9aa2effa57918a",
   221  		},
   222  	},
   223  }, {
   224  	series:  "trusty",
   225  	arches:  []string{"amd64"},
   226  	version: "1.16.0",
   227  	stream:  "testing",
   228  	tools: []*tools.ToolsMetadata{
   229  		{
   230  			Release:  "trusty",
   231  			Version:  "1.16.0",
   232  			Arch:     "amd64",
   233  			Size:     2973512,
   234  			Path:     "tools/testing/20130806/juju-1.16.0-trusty-amd64.tgz",
   235  			FileType: "tar.gz",
   236  			SHA256:   "447aeb6a934a5eaec4f703eda4ef2dac",
   237  		},
   238  	},
   239  }}
   240  
   241  func (s *simplestreamsSuite) TestFetch(c *gc.C) {
   242  	for i, t := range fetchTests {
   243  		c.Logf("test %d", i)
   244  		if t.stream == "" {
   245  			t.stream = "released"
   246  		}
   247  		var toolsConstraint *tools.ToolsConstraint
   248  		if t.version == "" {
   249  			toolsConstraint = tools.NewGeneralToolsConstraint(t.major, t.minor, simplestreams.LookupParams{
   250  				CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   251  				Series:    []string{t.series},
   252  				Arches:    t.arches,
   253  				Stream:    t.stream,
   254  			})
   255  		} else {
   256  			toolsConstraint = tools.NewVersionedToolsConstraint(version.MustParse(t.version),
   257  				simplestreams.LookupParams{
   258  					CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   259  					Series:    []string{t.series},
   260  					Arches:    t.arches,
   261  					Stream:    t.stream,
   262  				})
   263  		}
   264  		// Add invalid datasource and check later that resolveInfo is correct.
   265  		invalidSource := simplestreams.NewURLDataSource("invalid", "file://invalid", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, s.RequireSigned)
   266  		tools, resolveInfo, err := tools.Fetch(
   267  			[]simplestreams.DataSource{invalidSource, s.Source}, toolsConstraint)
   268  		if !c.Check(err, jc.ErrorIsNil) {
   269  			continue
   270  		}
   271  		for _, tm := range t.tools {
   272  			tm.FullPath, err = s.Source.URL(tm.Path)
   273  			c.Assert(err, jc.ErrorIsNil)
   274  		}
   275  		c.Check(tools, gc.DeepEquals, t.tools)
   276  		c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   277  			Source:    "test",
   278  			Signed:    s.RequireSigned,
   279  			IndexURL:  "test:/streams/v1/index.json",
   280  			MirrorURL: "",
   281  		})
   282  	}
   283  }
   284  
   285  func (s *simplestreamsSuite) TestFetchNoMatchingStream(c *gc.C) {
   286  	toolsConstraint := tools.NewGeneralToolsConstraint(2, -1, simplestreams.LookupParams{
   287  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   288  		Series:    []string{"precise"},
   289  		Arches:    []string{},
   290  		Stream:    "proposed",
   291  	})
   292  	_, _, err := tools.Fetch(
   293  		[]simplestreams.DataSource{s.Source}, toolsConstraint)
   294  	c.Assert(err, gc.ErrorMatches, `"content-download" data not found`)
   295  }
   296  
   297  func (s *simplestreamsSuite) TestFetchWithMirror(c *gc.C) {
   298  	toolsConstraint := tools.NewGeneralToolsConstraint(1, 13, simplestreams.LookupParams{
   299  		CloudSpec: simplestreams.CloudSpec{"us-west-2", "https://ec2.us-west-2.amazonaws.com"},
   300  		Series:    []string{"precise"},
   301  		Arches:    []string{"amd64"},
   302  		Stream:    "released",
   303  	})
   304  	toolsMetadata, resolveInfo, err := tools.Fetch(
   305  		[]simplestreams.DataSource{s.Source}, toolsConstraint)
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	c.Assert(len(toolsMetadata), gc.Equals, 1)
   308  
   309  	expectedMetadata := &tools.ToolsMetadata{
   310  		Release:  "precise",
   311  		Version:  "1.13.0",
   312  		Arch:     "amd64",
   313  		Size:     2973595,
   314  		Path:     "mirrored-path/juju-1.13.0-precise-amd64.tgz",
   315  		FullPath: "test:/mirrored-path/juju-1.13.0-precise-amd64.tgz",
   316  		FileType: "tar.gz",
   317  		SHA256:   "447aeb6a934a5eaec4f703eda4ef2dde",
   318  	}
   319  	c.Assert(err, jc.ErrorIsNil)
   320  	c.Assert(toolsMetadata[0], gc.DeepEquals, expectedMetadata)
   321  	c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   322  		Source:    "test",
   323  		Signed:    s.RequireSigned,
   324  		IndexURL:  "test:/streams/v1/index.json",
   325  		MirrorURL: "test:/",
   326  	})
   327  }
   328  
   329  func assertMetadataMatches(c *gc.C, storageDir string, stream string, toolList coretools.List, metadata []*tools.ToolsMetadata) {
   330  	var expectedMetadata []*tools.ToolsMetadata = make([]*tools.ToolsMetadata, len(toolList))
   331  	for i, tool := range toolList {
   332  		expectedMetadata[i] = &tools.ToolsMetadata{
   333  			Release:  tool.Version.Series,
   334  			Version:  tool.Version.Number.String(),
   335  			Arch:     tool.Version.Arch,
   336  			Size:     tool.Size,
   337  			Path:     fmt.Sprintf("%s/juju-%s.tgz", stream, tool.Version.String()),
   338  			FileType: "tar.gz",
   339  			SHA256:   tool.SHA256,
   340  		}
   341  	}
   342  	c.Assert(metadata, gc.DeepEquals, expectedMetadata)
   343  }
   344  
   345  func (s *simplestreamsSuite) TestWriteMetadataNoFetch(c *gc.C) {
   346  	toolsList := coretools.List{
   347  		{
   348  			Version: version.MustParseBinary("1.2.3-precise-amd64"),
   349  			Size:    123,
   350  			SHA256:  "abcd",
   351  		}, {
   352  			Version: version.MustParseBinary("2.0.1-raring-amd64"),
   353  			Size:    456,
   354  			SHA256:  "xyz",
   355  		},
   356  	}
   357  	expected := toolsList
   358  
   359  	// Add tools with an unknown series.
   360  	// We need to support this case for times when a new Ubuntu series
   361  	// is released and jujud does not know about it yet.
   362  	vers, err := version.ParseBinary("3.2.1-xuanhuaceratops-amd64")
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	toolsList = append(toolsList, &coretools.Tools{
   365  		Version: vers,
   366  		Size:    456,
   367  		SHA256:  "wqe",
   368  	})
   369  
   370  	dir := c.MkDir()
   371  	writer, err := filestorage.NewFileStorageWriter(dir)
   372  	c.Assert(err, jc.ErrorIsNil)
   373  	err = tools.MergeAndWriteMetadata(writer, "proposed", "proposed", toolsList, tools.DoNotWriteMirrors)
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", false)
   376  	assertMetadataMatches(c, dir, "proposed", expected, metadata)
   377  }
   378  
   379  func (s *simplestreamsSuite) assertWriteMetadata(c *gc.C, withMirrors bool) {
   380  	var versionStrings = []string{
   381  		"1.2.3-precise-amd64",
   382  		"2.0.1-raring-amd64",
   383  	}
   384  	dir := c.MkDir()
   385  	toolstesting.MakeTools(c, dir, "proposed", versionStrings)
   386  
   387  	toolsList := coretools.List{
   388  		{
   389  			// If sha256/size is already known, do not recalculate
   390  			Version: version.MustParseBinary("1.2.3-precise-amd64"),
   391  			Size:    123,
   392  			SHA256:  "abcd",
   393  		}, {
   394  			Version: version.MustParseBinary("2.0.1-raring-amd64"),
   395  			// The URL is not used for generating metadata.
   396  			URL: "bogus://",
   397  		},
   398  	}
   399  	writer, err := filestorage.NewFileStorageWriter(dir)
   400  	c.Assert(err, jc.ErrorIsNil)
   401  	writeMirrors := tools.DoNotWriteMirrors
   402  	if withMirrors {
   403  		writeMirrors = tools.WriteMirrors
   404  	}
   405  	err = tools.MergeAndWriteMetadata(writer, "proposed", "proposed", toolsList, writeMirrors)
   406  	c.Assert(err, jc.ErrorIsNil)
   407  	metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", withMirrors)
   408  	assertMetadataMatches(c, dir, "proposed", toolsList, metadata)
   409  
   410  	// No release stream generated so there will not be a legacy index file created.
   411  	_, err = writer.Get("tools/streams/v1/index.json")
   412  	c.Assert(err, gc.NotNil)
   413  }
   414  
   415  func (s *simplestreamsSuite) TestWriteMetadata(c *gc.C) {
   416  	s.assertWriteMetadata(c, false)
   417  }
   418  
   419  func (s *simplestreamsSuite) TestWriteMetadataWithMirrors(c *gc.C) {
   420  	s.assertWriteMetadata(c, true)
   421  }
   422  
   423  func (s *simplestreamsSuite) TestWriteMetadataMergeWithExisting(c *gc.C) {
   424  	dir := c.MkDir()
   425  	existingToolsList := coretools.List{
   426  		{
   427  			Version: version.MustParseBinary("1.2.3-precise-amd64"),
   428  			Size:    123,
   429  			SHA256:  "abc",
   430  		}, {
   431  			Version: version.MustParseBinary("2.0.1-raring-amd64"),
   432  			Size:    456,
   433  			SHA256:  "xyz",
   434  		},
   435  	}
   436  	writer, err := filestorage.NewFileStorageWriter(dir)
   437  	c.Assert(err, jc.ErrorIsNil)
   438  	err = tools.MergeAndWriteMetadata(writer, "testing", "testing", existingToolsList, tools.WriteMirrors)
   439  	c.Assert(err, jc.ErrorIsNil)
   440  	newToolsList := coretools.List{
   441  		existingToolsList[0],
   442  		{
   443  			Version: version.MustParseBinary("2.1.0-raring-amd64"),
   444  			Size:    789,
   445  			SHA256:  "def",
   446  		},
   447  	}
   448  	err = tools.MergeAndWriteMetadata(writer, "testing", "testing", newToolsList, tools.WriteMirrors)
   449  	c.Assert(err, jc.ErrorIsNil)
   450  	requiredToolsList := append(existingToolsList, newToolsList[1])
   451  	metadata := toolstesting.ParseMetadataFromDir(c, dir, "testing", true)
   452  	assertMetadataMatches(c, dir, "testing", requiredToolsList, metadata)
   453  
   454  	err = tools.MergeAndWriteMetadata(writer, "devel", "devel", newToolsList, tools.WriteMirrors)
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	metadata = toolstesting.ParseMetadataFromDir(c, dir, "testing", true)
   457  	assertMetadataMatches(c, dir, "testing", requiredToolsList, metadata)
   458  	metadata = toolstesting.ParseMetadataFromDir(c, dir, "devel", true)
   459  	assertMetadataMatches(c, dir, "devel", newToolsList, metadata)
   460  }
   461  
   462  type productSpecSuite struct{}
   463  
   464  var _ = gc.Suite(&productSpecSuite{})
   465  
   466  func (s *productSpecSuite) TestIndexIdNoStream(c *gc.C) {
   467  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   468  		Series: []string{"precise"},
   469  		Arches: []string{"amd64"},
   470  	})
   471  	ids := toolsConstraint.IndexIds()
   472  	c.Assert(ids, gc.HasLen, 0)
   473  }
   474  
   475  func (s *productSpecSuite) TestIndexId(c *gc.C) {
   476  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   477  		Series: []string{"precise"},
   478  		Arches: []string{"amd64"},
   479  		Stream: "proposed",
   480  	})
   481  	ids := toolsConstraint.IndexIds()
   482  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:proposed:tools"})
   483  }
   484  
   485  func (s *productSpecSuite) TestProductId(c *gc.C) {
   486  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   487  		Series: []string{"precise"},
   488  		Arches: []string{"amd64"},
   489  	})
   490  	ids, err := toolsConstraint.ProductIds()
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:12.04:amd64"})
   493  }
   494  
   495  func (s *productSpecSuite) TestIdMultiArch(c *gc.C) {
   496  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   497  		Series: []string{"precise"},
   498  		Arches: []string{"amd64", "arm"},
   499  	})
   500  	ids, err := toolsConstraint.ProductIds()
   501  	c.Assert(err, jc.ErrorIsNil)
   502  	c.Assert(ids, gc.DeepEquals, []string{
   503  		"com.ubuntu.juju:12.04:amd64",
   504  		"com.ubuntu.juju:12.04:arm"})
   505  }
   506  
   507  func (s *productSpecSuite) TestIdMultiSeries(c *gc.C) {
   508  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   509  		Series: []string{"precise", "raring"},
   510  		Arches: []string{"amd64"},
   511  		Stream: "released",
   512  	})
   513  	ids, err := toolsConstraint.ProductIds()
   514  	c.Assert(err, jc.ErrorIsNil)
   515  	c.Assert(ids, gc.DeepEquals, []string{
   516  		"com.ubuntu.juju:12.04:amd64",
   517  		"com.ubuntu.juju:13.04:amd64"})
   518  }
   519  
   520  func (s *productSpecSuite) TestIdIgnoresInvalidSeries(c *gc.C) {
   521  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   522  		Series: []string{"precise", "foobar"},
   523  		Arches: []string{"amd64"},
   524  		Stream: "released",
   525  	})
   526  	ids, err := toolsConstraint.ProductIds()
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:12.04:amd64"})
   529  }
   530  
   531  func (s *productSpecSuite) TestIdWithMajorVersionOnly(c *gc.C) {
   532  	toolsConstraint := tools.NewGeneralToolsConstraint(1, -1, simplestreams.LookupParams{
   533  		Series: []string{"precise"},
   534  		Arches: []string{"amd64"},
   535  		Stream: "released",
   536  	})
   537  	ids, err := toolsConstraint.ProductIds()
   538  	c.Assert(err, jc.ErrorIsNil)
   539  	c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`})
   540  }
   541  
   542  func (s *productSpecSuite) TestIdWithMajorMinorVersion(c *gc.C) {
   543  	toolsConstraint := tools.NewGeneralToolsConstraint(1, 2, simplestreams.LookupParams{
   544  		Series: []string{"precise"},
   545  		Arches: []string{"amd64"},
   546  		Stream: "released",
   547  	})
   548  	ids, err := toolsConstraint.ProductIds()
   549  	c.Assert(err, jc.ErrorIsNil)
   550  	c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:12.04:amd64`})
   551  }
   552  
   553  func (s *productSpecSuite) TestLargeNumber(c *gc.C) {
   554  	json := `{
   555          "updated": "Fri, 30 Aug 2013 16:12:58 +0800",
   556          "format": "products:1.0",
   557          "products": {
   558              "com.ubuntu.juju:1.10.0:amd64": {
   559                  "version": "1.10.0",
   560                  "arch": "amd64",
   561                  "versions": {
   562                      "20133008": {
   563                          "items": {
   564                              "1.10.0-precise-amd64": {
   565                                  "release": "precise",
   566                                  "version": "1.10.0",
   567                                  "arch": "amd64",
   568                                  "size": 9223372036854775807,
   569                                  "path": "releases/juju-1.10.0-precise-amd64.tgz",
   570                                  "ftype": "tar.gz",
   571                                  "sha256": ""
   572                              }
   573                          }
   574                      }
   575                  }
   576              }
   577          }
   578      }`
   579  	cloudMetadata, err := simplestreams.ParseCloudMetadata([]byte(json), "products:1.0", "", tools.ToolsMetadata{})
   580  	c.Assert(err, jc.ErrorIsNil)
   581  	c.Assert(cloudMetadata.Products, gc.HasLen, 1)
   582  	product := cloudMetadata.Products["com.ubuntu.juju:1.10.0:amd64"]
   583  	c.Assert(product, gc.NotNil)
   584  	c.Assert(product.Items, gc.HasLen, 1)
   585  	version := product.Items["20133008"]
   586  	c.Assert(version, gc.NotNil)
   587  	c.Assert(version.Items, gc.HasLen, 1)
   588  	item := version.Items["1.10.0-precise-amd64"]
   589  	c.Assert(item, gc.NotNil)
   590  	c.Assert(item, gc.FitsTypeOf, &tools.ToolsMetadata{})
   591  	c.Assert(item.(*tools.ToolsMetadata).Size, gc.Equals, int64(9223372036854775807))
   592  }
   593  
   594  type metadataHelperSuite struct {
   595  	coretesting.BaseSuite
   596  }
   597  
   598  var _ = gc.Suite(&metadataHelperSuite{})
   599  
   600  func (*metadataHelperSuite) TestMetadataFromTools(c *gc.C) {
   601  	metadata := tools.MetadataFromTools(nil, "proposed")
   602  	c.Assert(metadata, gc.HasLen, 0)
   603  
   604  	toolsList := coretools.List{{
   605  		Version: version.MustParseBinary("1.2.3-precise-amd64"),
   606  		Size:    123,
   607  		SHA256:  "abc",
   608  	}, {
   609  		Version: version.MustParseBinary("2.0.1-raring-amd64"),
   610  		URL:     "file:///tmp/proposed/juju-2.0.1-raring-amd64.tgz",
   611  		Size:    456,
   612  		SHA256:  "xyz",
   613  	}}
   614  	metadata = tools.MetadataFromTools(toolsList, "proposed")
   615  	c.Assert(metadata, gc.HasLen, len(toolsList))
   616  	for i, t := range toolsList {
   617  		md := metadata[i]
   618  		c.Assert(md.Release, gc.Equals, t.Version.Series)
   619  		c.Assert(md.Version, gc.Equals, t.Version.Number.String())
   620  		c.Assert(md.Arch, gc.Equals, t.Version.Arch)
   621  		// FullPath is only filled out when reading tools using simplestreams.
   622  		// It's not needed elsewhere and requires a URL() call.
   623  		c.Assert(md.FullPath, gc.Equals, "")
   624  		c.Assert(md.Path, gc.Equals, tools.StorageName(t.Version, "proposed")[len("tools/"):])
   625  		c.Assert(md.FileType, gc.Equals, "tar.gz")
   626  		c.Assert(md.Size, gc.Equals, t.Size)
   627  		c.Assert(md.SHA256, gc.Equals, t.SHA256)
   628  	}
   629  }
   630  
   631  type countingStorage struct {
   632  	storage.StorageReader
   633  	counter int
   634  }
   635  
   636  func (c *countingStorage) Get(name string) (io.ReadCloser, error) {
   637  	c.counter++
   638  	return c.StorageReader.Get(name)
   639  }
   640  
   641  func (*metadataHelperSuite) TestResolveMetadata(c *gc.C) {
   642  	var versionStrings = []string{"1.2.3-precise-amd64"}
   643  	dir := c.MkDir()
   644  	toolstesting.MakeTools(c, dir, "released", versionStrings)
   645  	toolsList := coretools.List{{
   646  		Version: version.MustParseBinary(versionStrings[0]),
   647  		Size:    123,
   648  		SHA256:  "abc",
   649  	}}
   650  
   651  	stor, err := filestorage.NewFileStorageReader(dir)
   652  	c.Assert(err, jc.ErrorIsNil)
   653  	err = tools.ResolveMetadata(stor, "released", nil)
   654  	c.Assert(err, jc.ErrorIsNil)
   655  
   656  	// We already have size/sha256, so ensure that storage isn't consulted.
   657  	countingStorage := &countingStorage{StorageReader: stor}
   658  	metadata := tools.MetadataFromTools(toolsList, "released")
   659  	err = tools.ResolveMetadata(countingStorage, "released", metadata)
   660  	c.Assert(err, jc.ErrorIsNil)
   661  	c.Assert(countingStorage.counter, gc.Equals, 0)
   662  
   663  	// Now clear size/sha256, and check that it is called, and
   664  	// the size/sha256 sum are updated.
   665  	metadata[0].Size = 0
   666  	metadata[0].SHA256 = ""
   667  	err = tools.ResolveMetadata(countingStorage, "released", metadata)
   668  	c.Assert(err, jc.ErrorIsNil)
   669  	c.Assert(countingStorage.counter, gc.Equals, 1)
   670  	c.Assert(metadata[0].Size, gc.Not(gc.Equals), 0)
   671  	c.Assert(metadata[0].SHA256, gc.Not(gc.Equals), "")
   672  }
   673  
   674  func (*metadataHelperSuite) TestResolveMetadataLegacyPPC64(c *gc.C) {
   675  	var versionStrings = []string{"1.2.3-precise-amd64", "1.2.3-precise-ppc64el"}
   676  	dir := c.MkDir()
   677  	toolstesting.MakeTools(c, dir, "released", versionStrings)
   678  
   679  	toolsList := coretools.List{
   680  		{
   681  			Version: version.MustParseBinary(versionStrings[0]),
   682  		}, {
   683  			Version: version.MustParseBinary(versionStrings[1]),
   684  		}, {
   685  			Version: version.MustParseBinary("1.2.3-precise-ppc64"),
   686  		},
   687  	}
   688  	toolsMetadata := tools.MetadataFromTools(toolsList, dir)
   689  	stor, err := filestorage.NewFileStorageReader(dir)
   690  	c.Assert(err, jc.ErrorIsNil)
   691  	err = tools.ResolveMetadata(stor, "released", toolsMetadata)
   692  	c.Assert(err, jc.ErrorIsNil)
   693  	c.Assert(toolsMetadata, gc.DeepEquals, []*tools.ToolsMetadata{
   694  		{
   695  			Release:  "precise",
   696  			Version:  "1.2.3",
   697  			Arch:     "amd64",
   698  			Size:     19,
   699  			FileType: "tar.gz",
   700  			SHA256:   "dcdd65b962b804a3d63b108d670290ee95a867a97fe9b9f99b2b77b5c7173e59",
   701  			Path:     fmt.Sprintf("%s/juju-1.2.3-precise-amd64.tgz", dir),
   702  		},
   703  		{
   704  			Release:  "precise",
   705  			Version:  "1.2.3",
   706  			Arch:     "ppc64el",
   707  			Size:     21,
   708  			FileType: "tar.gz",
   709  			SHA256:   "a3460ed45eb07a69adfcd541413a495f988c5842d715c6a40353075c3ad47af2",
   710  			Path:     fmt.Sprintf("%s/juju-1.2.3-precise-ppc64el.tgz", dir),
   711  		},
   712  		{
   713  			Release:  "precise",
   714  			Version:  "1.2.3",
   715  			Arch:     "ppc64",
   716  			Size:     21,
   717  			FileType: "tar.gz",
   718  			SHA256:   "a3460ed45eb07a69adfcd541413a495f988c5842d715c6a40353075c3ad47af2",
   719  			Path:     fmt.Sprintf("%s/juju-1.2.3-precise-ppc64el.tgz", dir),
   720  		},
   721  	})
   722  }
   723  
   724  func (*metadataHelperSuite) TestMergeMetadata(c *gc.C) {
   725  	md1 := &tools.ToolsMetadata{
   726  		Release: "precise",
   727  		Version: "1.2.3",
   728  		Arch:    "amd64",
   729  		Path:    "path1",
   730  	}
   731  	md2 := &tools.ToolsMetadata{
   732  		Release: "precise",
   733  		Version: "1.2.3",
   734  		Arch:    "amd64",
   735  		Path:    "path2",
   736  	}
   737  	md3 := &tools.ToolsMetadata{
   738  		Release: "raring",
   739  		Version: "1.2.3",
   740  		Arch:    "amd64",
   741  		Path:    "path3",
   742  	}
   743  
   744  	withSize := func(md *tools.ToolsMetadata, size int64) *tools.ToolsMetadata {
   745  		clone := *md
   746  		clone.Size = size
   747  		return &clone
   748  	}
   749  	withSHA256 := func(md *tools.ToolsMetadata, sha256 string) *tools.ToolsMetadata {
   750  		clone := *md
   751  		clone.SHA256 = sha256
   752  		return &clone
   753  	}
   754  
   755  	type mdlist []*tools.ToolsMetadata
   756  	type test struct {
   757  		name             string
   758  		lhs, rhs, merged []*tools.ToolsMetadata
   759  		err              string
   760  	}
   761  	tests := []test{{
   762  		name:   "non-empty lhs, empty rhs",
   763  		lhs:    mdlist{md1},
   764  		rhs:    nil,
   765  		merged: mdlist{md1},
   766  	}, {
   767  		name:   "empty lhs, non-empty rhs",
   768  		lhs:    nil,
   769  		rhs:    mdlist{md2},
   770  		merged: mdlist{md2},
   771  	}, {
   772  		name:   "identical lhs, rhs",
   773  		lhs:    mdlist{md1},
   774  		rhs:    mdlist{md1},
   775  		merged: mdlist{md1},
   776  	}, {
   777  		name:   "same tools in lhs and rhs, neither have size: prefer lhs",
   778  		lhs:    mdlist{md1},
   779  		rhs:    mdlist{md2},
   780  		merged: mdlist{md1},
   781  	}, {
   782  		name:   "same tools in lhs and rhs, only lhs has a size: prefer lhs",
   783  		lhs:    mdlist{withSize(md1, 123)},
   784  		rhs:    mdlist{md2},
   785  		merged: mdlist{withSize(md1, 123)},
   786  	}, {
   787  		name:   "same tools in lhs and rhs, only rhs has a size: prefer rhs",
   788  		lhs:    mdlist{md1},
   789  		rhs:    mdlist{withSize(md2, 123)},
   790  		merged: mdlist{withSize(md2, 123)},
   791  	}, {
   792  		name:   "same tools in lhs and rhs, both have the same size: prefer lhs",
   793  		lhs:    mdlist{withSize(md1, 123)},
   794  		rhs:    mdlist{withSize(md2, 123)},
   795  		merged: mdlist{withSize(md1, 123)},
   796  	}, {
   797  		name: "same tools in lhs and rhs, both have different sizes: error",
   798  		lhs:  mdlist{withSize(md1, 123)},
   799  		rhs:  mdlist{withSize(md2, 456)},
   800  		err:  "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,456\\) sha256=\\(,\\)",
   801  	}, {
   802  		name: "same tools in lhs and rhs, both have same size but different sha256: error",
   803  		lhs:  mdlist{withSHA256(withSize(md1, 123), "a")},
   804  		rhs:  mdlist{withSHA256(withSize(md2, 123), "b")},
   805  		err:  "metadata mismatch for 1\\.2\\.3-precise-amd64: sizes=\\(123,123\\) sha256=\\(a,b\\)",
   806  	}, {
   807  		name:   "lhs is a proper superset of rhs: union of lhs and rhs",
   808  		lhs:    mdlist{md1, md3},
   809  		rhs:    mdlist{md1},
   810  		merged: mdlist{md1, md3},
   811  	}, {
   812  		name:   "rhs is a proper superset of lhs: union of lhs and rhs",
   813  		lhs:    mdlist{md1},
   814  		rhs:    mdlist{md1, md3},
   815  		merged: mdlist{md1, md3},
   816  	}}
   817  	for i, test := range tests {
   818  		c.Logf("test %d: %s", i, test.name)
   819  		merged, err := tools.MergeMetadata(test.lhs, test.rhs)
   820  		if test.err == "" {
   821  			c.Assert(err, jc.ErrorIsNil)
   822  			c.Assert(merged, gc.DeepEquals, test.merged)
   823  		} else {
   824  			c.Assert(err, gc.ErrorMatches, test.err)
   825  			c.Assert(merged, gc.IsNil)
   826  		}
   827  	}
   828  }
   829  
   830  func (*metadataHelperSuite) TestReadWriteMetadataSingleStream(c *gc.C) {
   831  	metadata := map[string][]*tools.ToolsMetadata{
   832  		"released": {{
   833  			Release: "precise",
   834  			Version: "1.2.3",
   835  			Arch:    "amd64",
   836  			Path:    "path1",
   837  		}, {
   838  			Release: "raring",
   839  			Version: "1.2.3",
   840  			Arch:    "amd64",
   841  			Path:    "path2",
   842  		}},
   843  	}
   844  
   845  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   846  	c.Assert(err, jc.ErrorIsNil)
   847  	out, err := tools.ReadAllMetadata(stor)
   848  	c.Assert(err, jc.ErrorIsNil) // non-existence is not an error
   849  	c.Assert(out, gc.HasLen, 0)
   850  	err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   851  	c.Assert(err, jc.ErrorIsNil)
   852  
   853  	// Read back what was just written.
   854  	out, err = tools.ReadAllMetadata(stor)
   855  	c.Assert(err, jc.ErrorIsNil)
   856  	for _, outMetadata := range out {
   857  		for _, md := range outMetadata {
   858  			// FullPath is set by ReadAllMetadata.
   859  			c.Assert(md.FullPath, gc.Not(gc.Equals), "")
   860  			md.FullPath = ""
   861  		}
   862  	}
   863  	c.Assert(out, jc.DeepEquals, metadata)
   864  }
   865  
   866  func (*metadataHelperSuite) writeMetadataMultipleStream(c *gc.C) (storage.StorageReader, map[string][]*tools.ToolsMetadata) {
   867  	metadata := map[string][]*tools.ToolsMetadata{
   868  		"released": {{
   869  			Release: "precise",
   870  			Version: "1.2.3",
   871  			Arch:    "amd64",
   872  			Path:    "path1",
   873  		}},
   874  		"proposed": {{
   875  			Release: "raring",
   876  			Version: "1.2.3",
   877  			Arch:    "amd64",
   878  			Path:    "path2",
   879  		}},
   880  	}
   881  
   882  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   883  	c.Assert(err, jc.ErrorIsNil)
   884  	out, err := tools.ReadAllMetadata(stor)
   885  	c.Assert(out, gc.HasLen, 0)
   886  	c.Assert(err, jc.ErrorIsNil) // non-existence is not an error
   887  	err = tools.WriteMetadata(stor, metadata, []string{"released", "proposed"}, tools.DoNotWriteMirrors)
   888  	c.Assert(err, jc.ErrorIsNil)
   889  	return stor, metadata
   890  }
   891  
   892  func (s *metadataHelperSuite) TestReadWriteMetadataMultipleStream(c *gc.C) {
   893  	stor, metadata := s.writeMetadataMultipleStream(c)
   894  	// Read back what was just written.
   895  	out, err := tools.ReadAllMetadata(stor)
   896  	c.Assert(err, jc.ErrorIsNil)
   897  	for _, outMetadata := range out {
   898  		for _, md := range outMetadata {
   899  			// FullPath is set by ReadAllMetadata.
   900  			c.Assert(md.FullPath, gc.Not(gc.Equals), "")
   901  			md.FullPath = ""
   902  		}
   903  	}
   904  	c.Assert(out, jc.DeepEquals, metadata)
   905  }
   906  
   907  func (s *metadataHelperSuite) TestWriteMetadataLegacyIndex(c *gc.C) {
   908  	stor, _ := s.writeMetadataMultipleStream(c)
   909  	// Read back the legacy index
   910  	rdr, err := stor.Get("tools/streams/v1/index.json")
   911  	defer rdr.Close()
   912  	c.Assert(err, jc.ErrorIsNil)
   913  	data, err := ioutil.ReadAll(rdr)
   914  	c.Assert(err, jc.ErrorIsNil)
   915  	var indices simplestreams.Indices
   916  	err = json.Unmarshal(data, &indices)
   917  	c.Assert(err, jc.ErrorIsNil)
   918  	c.Assert(indices.Indexes, gc.HasLen, 1)
   919  	indices.Updated = ""
   920  	c.Assert(indices.Indexes["com.ubuntu.juju:released:tools"], gc.NotNil)
   921  	indices.Indexes["com.ubuntu.juju:released:tools"].Updated = ""
   922  	expected := simplestreams.Indices{
   923  		Format: "index:1.0",
   924  		Indexes: map[string]*simplestreams.IndexMetadata{
   925  			"com.ubuntu.juju:released:tools": {
   926  				Format:           "products:1.0",
   927  				DataType:         "content-download",
   928  				ProductsFilePath: "streams/v1/com.ubuntu.juju-released-tools.json",
   929  				ProductIds:       []string{"com.ubuntu.juju:12.04:amd64"},
   930  			},
   931  		},
   932  	}
   933  	c.Assert(indices, jc.DeepEquals, expected)
   934  }
   935  
   936  func (s *metadataHelperSuite) TestReadWriteMetadataUnchanged(c *gc.C) {
   937  	metadata := map[string][]*tools.ToolsMetadata{
   938  		"released": {{
   939  			Release: "precise",
   940  			Version: "1.2.3",
   941  			Arch:    "amd64",
   942  			Path:    "path1",
   943  		}, {
   944  			Release: "raring",
   945  			Version: "1.2.3",
   946  			Arch:    "amd64",
   947  			Path:    "path2",
   948  		}},
   949  	}
   950  
   951  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   952  	c.Assert(err, jc.ErrorIsNil)
   953  	err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   954  	c.Assert(err, jc.ErrorIsNil)
   955  
   956  	s.PatchValue(tools.WriteMetadataFiles, func(stor storage.Storage, metadataInfo []tools.MetadataFile) error {
   957  		// The product data is the same, we only write the indices.
   958  		c.Assert(metadataInfo, gc.HasLen, 2)
   959  		c.Assert(metadataInfo[0].Path, gc.Equals, "streams/v1/index2.json")
   960  		c.Assert(metadataInfo[1].Path, gc.Equals, "streams/v1/index.json")
   961  		return nil
   962  	})
   963  	err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   964  	c.Assert(err, jc.ErrorIsNil)
   965  }
   966  
   967  func (*metadataHelperSuite) TestReadMetadataPrefersNewIndex(c *gc.C) {
   968  	if runtime.GOOS == "windows" {
   969  		c.Skip("Skipped for now because of introduced regression")
   970  	}
   971  	metadataDir := c.MkDir()
   972  
   973  	// Generate metadata and rename index to index.json
   974  	metadata := map[string][]*tools.ToolsMetadata{
   975  		"proposed": {{
   976  			Release: "precise",
   977  			Version: "1.2.3",
   978  			Arch:    "amd64",
   979  			Path:    "path1",
   980  		}},
   981  		"released": {{
   982  			Release: "trusty",
   983  			Version: "1.2.3",
   984  			Arch:    "amd64",
   985  			Path:    "path1",
   986  		}},
   987  	}
   988  	stor, err := filestorage.NewFileStorageWriter(metadataDir)
   989  	c.Assert(err, jc.ErrorIsNil)
   990  	err = tools.WriteMetadata(stor, metadata, []string{"proposed", "released"}, tools.DoNotWriteMirrors)
   991  	c.Assert(err, jc.ErrorIsNil)
   992  	err = os.Rename(
   993  		filepath.Join(metadataDir, "tools", "streams", "v1", "index2.json"),
   994  		filepath.Join(metadataDir, "tools", "streams", "v1", "index.json"),
   995  	)
   996  	c.Assert(err, jc.ErrorIsNil)
   997  
   998  	// Generate different metadata with index2.json
   999  	metadata = map[string][]*tools.ToolsMetadata{
  1000  		"released": {{
  1001  			Release: "precise",
  1002  			Version: "1.2.3",
  1003  			Arch:    "amd64",
  1004  			Path:    "path1",
  1005  		}},
  1006  	}
  1007  	err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors)
  1008  	c.Assert(err, jc.ErrorIsNil)
  1009  
  1010  	// Read back all metadata, expecting to find metadata in index2.json.
  1011  	out, err := tools.ReadAllMetadata(stor)
  1012  	for _, outMetadata := range out {
  1013  		for _, md := range outMetadata {
  1014  			// FullPath is set by ReadAllMetadata.
  1015  			c.Assert(md.FullPath, gc.Not(gc.Equals), "")
  1016  			md.FullPath = ""
  1017  		}
  1018  	}
  1019  	c.Assert(out, jc.DeepEquals, metadata)
  1020  }
  1021  
  1022  type signedSuite struct {
  1023  	coretesting.BaseSuite
  1024  }
  1025  
  1026  func (s *signedSuite) SetUpSuite(c *gc.C) {
  1027  	s.BaseSuite.SetUpSuite(c)
  1028  	var imageData = map[string]string{
  1029  		"/unsigned/streams/v1/index.json":          unsignedIndex,
  1030  		"/unsigned/streams/v1/tools_metadata.json": unsignedProduct,
  1031  	}
  1032  
  1033  	// Set up some signed data from the unsigned data.
  1034  	// Overwrite the product path to use the sjson suffix.
  1035  	rawUnsignedIndex := strings.Replace(
  1036  		unsignedIndex, "streams/v1/tools_metadata.json", "streams/v1/tools_metadata.sjson", -1)
  1037  	r := bytes.NewReader([]byte(rawUnsignedIndex))
  1038  	signedData, err := simplestreams.Encode(
  1039  		r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
  1040  	c.Assert(err, jc.ErrorIsNil)
  1041  	imageData["/signed/streams/v1/index.sjson"] = string(signedData)
  1042  
  1043  	// Replace the tools path in the unsigned data with a different one so we can test that the right
  1044  	// tools path is used.
  1045  	rawUnsignedProduct := strings.Replace(
  1046  		unsignedProduct, "juju-1.13.0", "juju-1.13.1", -1)
  1047  	r = bytes.NewReader([]byte(rawUnsignedProduct))
  1048  	signedData, err = simplestreams.Encode(
  1049  		r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
  1050  	c.Assert(err, jc.ErrorIsNil)
  1051  	imageData["/signed/streams/v1/tools_metadata.sjson"] = string(signedData)
  1052  	sstesting.SetRoundTripperFiles(imageData, map[string]int{"signedtest://unauth": http.StatusUnauthorized})
  1053  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
  1054  }
  1055  
  1056  func (s *signedSuite) TearDownSuite(c *gc.C) {
  1057  	sstesting.SetRoundTripperFiles(nil, nil)
  1058  	s.BaseSuite.TearDownSuite(c)
  1059  }
  1060  
  1061  func (s *signedSuite) TestSignedToolsMetadata(c *gc.C) {
  1062  	signedSource := simplestreams.NewURLSignedDataSource(
  1063  		"test", "signedtest://host/signed", sstesting.SignedMetadataPublicKey,
  1064  		utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true)
  1065  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
  1066  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
  1067  		Series:    []string{"precise"},
  1068  		Arches:    []string{"amd64"},
  1069  		Stream:    "released",
  1070  	})
  1071  	toolsMetadata, resolveInfo, err := tools.Fetch(
  1072  		[]simplestreams.DataSource{signedSource}, toolsConstraint)
  1073  	c.Assert(err, jc.ErrorIsNil)
  1074  	c.Assert(len(toolsMetadata), gc.Equals, 1)
  1075  	c.Assert(toolsMetadata[0].Path, gc.Equals, "tools/releases/20130806/juju-1.13.1-precise-amd64.tgz")
  1076  	c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
  1077  		Source:    "test",
  1078  		Signed:    true,
  1079  		IndexURL:  "signedtest://host/signed/streams/v1/index.sjson",
  1080  		MirrorURL: "",
  1081  	})
  1082  }
  1083  
  1084  var unsignedIndex = `
  1085  {
  1086   "index": {
  1087    "com.ubuntu.juju:released:tools": {
  1088     "updated": "Mon, 05 Aug 2013 11:07:04 +0000",
  1089     "datatype": "content-download",
  1090     "format": "products:1.0",
  1091     "products": [
  1092       "com.ubuntu.juju:12.04:amd64"
  1093     ],
  1094     "path": "streams/v1/tools_metadata.json"
  1095    }
  1096   },
  1097   "updated": "Wed, 01 May 2013 13:31:26 +0000",
  1098   "format": "index:1.0"
  1099  }
  1100  `
  1101  var unsignedProduct = `
  1102  {
  1103   "updated": "Wed, 01 May 2013 13:31:26 +0000",
  1104   "content_id": "com.ubuntu.cloud:released:aws",
  1105   "datatype": "content-download",
  1106   "products": {
  1107     "com.ubuntu.juju:12.04:amd64": {
  1108      "arch": "amd64",
  1109      "release": "precise",
  1110      "versions": {
  1111       "20130806": {
  1112        "items": {
  1113         "1130preciseamd64": {
  1114          "version": "1.13.0",
  1115          "size": 2973595,
  1116          "path": "tools/releases/20130806/juju-1.13.0-precise-amd64.tgz",
  1117          "ftype": "tar.gz",
  1118          "sha256": "447aeb6a934a5eaec4f703eda4ef2dde"
  1119         }
  1120        }
  1121       }
  1122      }
  1123     }
  1124   },
  1125   "format": "products:1.0"
  1126  }
  1127  `