github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/imagemetadata/simplestreams_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package imagemetadata_test
     5  
     6  import (
     7  	"bytes"
     8  	"flag"
     9  	"net/http"
    10  	"reflect"
    11  	"strings"
    12  	stdtesting "testing"
    13  
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	"gopkg.in/amz.v3/aws"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/environs/imagemetadata"
    20  	"github.com/juju/juju/environs/simplestreams"
    21  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    22  )
    23  
    24  var live = flag.Bool("live", false, "Include live simplestreams tests")
    25  var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data")
    26  
    27  type liveTestData struct {
    28  	baseURL        string
    29  	requireSigned  bool
    30  	validCloudSpec simplestreams.CloudSpec
    31  }
    32  
    33  var liveUrls = map[string]liveTestData{
    34  	"ec2": {
    35  		baseURL:        imagemetadata.DefaultUbuntuBaseURL,
    36  		requireSigned:  true,
    37  		validCloudSpec: simplestreams.CloudSpec{"us-east-1", aws.Regions["us-east-1"].EC2Endpoint},
    38  	},
    39  	"canonistack": {
    40  		baseURL:        "https://swift.canonistack.canonical.com/v1/AUTH_a48765cc0e864be980ee21ae26aaaed4/simplestreams/data",
    41  		requireSigned:  false,
    42  		validCloudSpec: simplestreams.CloudSpec{"lcy01", "https://keystone.canonistack.canonical.com:443/v1.0/"},
    43  	},
    44  }
    45  
    46  func Test(t *stdtesting.T) {
    47  	if *live {
    48  		if *vendor == "" {
    49  			t.Fatal("missing vendor")
    50  		}
    51  		var ok bool
    52  		var testData liveTestData
    53  		if testData, ok = liveUrls[*vendor]; !ok {
    54  			keys := reflect.ValueOf(liveUrls).MapKeys()
    55  			t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys)
    56  		}
    57  		registerLiveSimpleStreamsTests(testData.baseURL, imagemetadata.NewImageConstraint(simplestreams.LookupParams{
    58  			CloudSpec: testData.validCloudSpec,
    59  			Series:    []string{"quantal"},
    60  			Arches:    []string{"amd64"},
    61  		}), testData.requireSigned)
    62  	}
    63  	registerSimpleStreamsTests()
    64  	gc.TestingT(t)
    65  }
    66  
    67  func registerSimpleStreamsTests() {
    68  	gc.Suite(&simplestreamsSuite{
    69  		LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{
    70  			Source: simplestreams.NewURLDataSource(
    71  				"test roundtripper", "test:", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, false),
    72  			RequireSigned:  false,
    73  			DataType:       imagemetadata.ImageIds,
    74  			StreamsVersion: imagemetadata.CurrentStreamsVersion,
    75  			ValidConstraint: imagemetadata.NewImageConstraint(simplestreams.LookupParams{
    76  				CloudSpec: simplestreams.CloudSpec{
    77  					Region:   "us-east-1",
    78  					Endpoint: "https://ec2.us-east-1.amazonaws.com",
    79  				},
    80  				Series: []string{"precise"},
    81  				Arches: []string{"amd64", "arm"},
    82  			}),
    83  		},
    84  	})
    85  	gc.Suite(&signedSuite{})
    86  }
    87  
    88  func registerLiveSimpleStreamsTests(baseURL string, validImageConstraint simplestreams.LookupConstraint, requireSigned bool) {
    89  	gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{
    90  		Source:          simplestreams.NewURLDataSource("test", baseURL, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, requireSigned),
    91  		RequireSigned:   requireSigned,
    92  		DataType:        imagemetadata.ImageIds,
    93  		ValidConstraint: validImageConstraint,
    94  	})
    95  }
    96  
    97  type simplestreamsSuite struct {
    98  	sstesting.LocalLiveSimplestreamsSuite
    99  	sstesting.TestDataSuite
   100  }
   101  
   102  func (s *simplestreamsSuite) SetUpSuite(c *gc.C) {
   103  	s.LocalLiveSimplestreamsSuite.SetUpSuite(c)
   104  	s.TestDataSuite.SetUpSuite(c)
   105  }
   106  
   107  func (s *simplestreamsSuite) TearDownSuite(c *gc.C) {
   108  	s.TestDataSuite.TearDownSuite(c)
   109  	s.LocalLiveSimplestreamsSuite.TearDownSuite(c)
   110  }
   111  
   112  func (s *simplestreamsSuite) TestOfficialSources(c *gc.C) {
   113  	origKey := imagemetadata.SetSigningPublicKey(sstesting.SignedMetadataPublicKey)
   114  	defer func() {
   115  		imagemetadata.SetSigningPublicKey(origKey)
   116  	}()
   117  	ds, err := imagemetadata.OfficialDataSources("daily")
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	c.Assert(ds, gc.HasLen, 2)
   120  	url, err := ds[0].URL("")
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	c.Assert(url, gc.Equals, "https://streams.canonical.com/juju/images/daily/")
   123  	c.Assert(ds[0].PublicSigningKey(), gc.Equals, sstesting.SignedMetadataPublicKey)
   124  
   125  	url, err = ds[1].URL("")
   126  	c.Assert(err, jc.ErrorIsNil)
   127  	c.Assert(url, gc.Equals, "http://cloud-images.ubuntu.com/daily/")
   128  	c.Assert(ds[1].PublicSigningKey(), gc.Equals, sstesting.SignedMetadataPublicKey)
   129  }
   130  
   131  var fetchTests = []struct {
   132  	region  string
   133  	version string
   134  	arches  []string
   135  	images  []*imagemetadata.ImageMetadata
   136  }{
   137  	{
   138  		region:  "us-east-1",
   139  		version: "12.04",
   140  		arches:  []string{"amd64", "arm"},
   141  		images: []*imagemetadata.ImageMetadata{
   142  			{
   143  				Id:         "ami-442ea674",
   144  				VirtType:   "hvm",
   145  				Arch:       "amd64",
   146  				RegionName: "us-east-1",
   147  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   148  				Storage:    "ebs",
   149  			},
   150  			{
   151  				Id:         "ami-442ea684",
   152  				VirtType:   "pv",
   153  				Arch:       "amd64",
   154  				RegionName: "us-east-1",
   155  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   156  				Storage:    "instance",
   157  			},
   158  			{
   159  				Id:         "ami-442ea699",
   160  				VirtType:   "pv",
   161  				Arch:       "arm",
   162  				RegionName: "us-east-1",
   163  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   164  				Storage:    "ebs",
   165  			},
   166  		},
   167  	},
   168  	{
   169  		region:  "us-east-1",
   170  		version: "12.04",
   171  		arches:  []string{"amd64"},
   172  		images: []*imagemetadata.ImageMetadata{
   173  			{
   174  				Id:         "ami-442ea674",
   175  				VirtType:   "hvm",
   176  				Arch:       "amd64",
   177  				RegionName: "us-east-1",
   178  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   179  				Storage:    "ebs",
   180  			},
   181  			{
   182  				Id:         "ami-442ea684",
   183  				VirtType:   "pv",
   184  				Arch:       "amd64",
   185  				RegionName: "us-east-1",
   186  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   187  				Storage:    "instance",
   188  			},
   189  		},
   190  	},
   191  	{
   192  		region:  "us-east-1",
   193  		version: "12.04",
   194  		arches:  []string{"arm"},
   195  		images: []*imagemetadata.ImageMetadata{
   196  			{
   197  				Id:         "ami-442ea699",
   198  				VirtType:   "pv",
   199  				Arch:       "arm",
   200  				RegionName: "us-east-1",
   201  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   202  				Storage:    "ebs",
   203  			},
   204  		},
   205  	},
   206  	{
   207  		region:  "us-east-1",
   208  		version: "12.04",
   209  		arches:  []string{"amd64"},
   210  		images: []*imagemetadata.ImageMetadata{
   211  			{
   212  				Id:         "ami-442ea674",
   213  				VirtType:   "hvm",
   214  				Arch:       "amd64",
   215  				RegionName: "us-east-1",
   216  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   217  				Storage:    "ebs",
   218  			},
   219  			{
   220  				Id:         "ami-442ea684",
   221  				VirtType:   "pv",
   222  				Arch:       "amd64",
   223  				RegionName: "us-east-1",
   224  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   225  				Storage:    "instance",
   226  			},
   227  		},
   228  	},
   229  	{
   230  		version: "12.04",
   231  		arches:  []string{"amd64"},
   232  		images: []*imagemetadata.ImageMetadata{
   233  			{
   234  				Id:         "ami-26745463",
   235  				VirtType:   "pv",
   236  				Arch:       "amd64",
   237  				RegionName: "au-east-2",
   238  				Endpoint:   "https://somewhere-else",
   239  				Storage:    "ebs",
   240  			},
   241  			{
   242  				Id:         "ami-26745464",
   243  				VirtType:   "pv",
   244  				Arch:       "amd64",
   245  				RegionName: "au-east-1",
   246  				Endpoint:   "https://somewhere",
   247  				Storage:    "ebs",
   248  			},
   249  			{
   250  				Id:         "ami-442ea674",
   251  				VirtType:   "hvm",
   252  				Arch:       "amd64",
   253  				RegionName: "us-east-1",
   254  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   255  				Storage:    "ebs",
   256  			},
   257  			{
   258  				Id:          "ami-442ea675",
   259  				VirtType:    "hvm",
   260  				Arch:        "amd64",
   261  				RegionAlias: "uswest3",
   262  				RegionName:  "us-west-3",
   263  				Endpoint:    "https://ec2.us-west-3.amazonaws.com",
   264  				Storage:     "ebs",
   265  			},
   266  			{
   267  				Id:         "ami-442ea684",
   268  				VirtType:   "pv",
   269  				Arch:       "amd64",
   270  				RegionName: "us-east-1",
   271  				Endpoint:   "https://ec2.us-east-1.amazonaws.com",
   272  				Storage:    "instance",
   273  			},
   274  		},
   275  	},
   276  }
   277  
   278  func (s *simplestreamsSuite) TestFetch(c *gc.C) {
   279  	for i, t := range fetchTests {
   280  		c.Logf("test %d", i)
   281  		cloudSpec := simplestreams.CloudSpec{t.region, "https://ec2.us-east-1.amazonaws.com"}
   282  		if t.region == "" {
   283  			cloudSpec = simplestreams.EmptyCloudSpec
   284  		}
   285  		imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   286  			CloudSpec: cloudSpec,
   287  			Series:    []string{"precise"},
   288  			Arches:    t.arches,
   289  		})
   290  		// Add invalid datasource and check later that resolveInfo is correct.
   291  		invalidSource := simplestreams.NewURLDataSource("invalid", "file://invalid", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, s.RequireSigned)
   292  		images, resolveInfo, err := imagemetadata.Fetch(
   293  			[]simplestreams.DataSource{invalidSource, s.Source}, imageConstraint)
   294  		if !c.Check(err, jc.ErrorIsNil) {
   295  			continue
   296  		}
   297  		for _, testImage := range t.images {
   298  			testImage.Version = t.version
   299  		}
   300  		c.Check(images, gc.DeepEquals, t.images)
   301  		c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   302  			Source:    "test roundtripper",
   303  			Signed:    s.RequireSigned,
   304  			IndexURL:  "test:/streams/v1/index.json",
   305  			MirrorURL: "",
   306  		})
   307  	}
   308  }
   309  
   310  type productSpecSuite struct{}
   311  
   312  var _ = gc.Suite(&productSpecSuite{})
   313  
   314  func (s *productSpecSuite) TestIdWithDefaultStream(c *gc.C) {
   315  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   316  		Series: []string{"precise"},
   317  		Arches: []string{"amd64"},
   318  	})
   319  	for _, stream := range []string{"", "released"} {
   320  		imageConstraint.Stream = stream
   321  		ids, err := imageConstraint.ProductIds()
   322  		c.Assert(err, jc.ErrorIsNil)
   323  		c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.cloud:server:12.04:amd64"})
   324  	}
   325  }
   326  
   327  func (s *productSpecSuite) TestId(c *gc.C) {
   328  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   329  		Series: []string{"precise"},
   330  		Arches: []string{"amd64"},
   331  		Stream: "daily",
   332  	})
   333  	ids, err := imageConstraint.ProductIds()
   334  	c.Assert(err, jc.ErrorIsNil)
   335  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.cloud.daily:server:12.04:amd64"})
   336  }
   337  
   338  func (s *productSpecSuite) TestIdMultiArch(c *gc.C) {
   339  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   340  		Series: []string{"precise"},
   341  		Arches: []string{"amd64", "i386"},
   342  		Stream: "daily",
   343  	})
   344  	ids, err := imageConstraint.ProductIds()
   345  	c.Assert(err, jc.ErrorIsNil)
   346  	c.Assert(ids, gc.DeepEquals, []string{
   347  		"com.ubuntu.cloud.daily:server:12.04:amd64",
   348  		"com.ubuntu.cloud.daily:server:12.04:i386"})
   349  }
   350  
   351  type signedSuite struct {
   352  	origKey string
   353  }
   354  
   355  func (s *signedSuite) SetUpSuite(c *gc.C) {
   356  	var imageData = map[string]string{
   357  		"/unsigned/streams/v1/index.json":          unsignedIndex,
   358  		"/unsigned/streams/v1/image_metadata.json": unsignedProduct,
   359  	}
   360  
   361  	// Set up some signed data from the unsigned data.
   362  	// Overwrite the product path to use the sjson suffix.
   363  	rawUnsignedIndex := strings.Replace(
   364  		unsignedIndex, "streams/v1/image_metadata.json", "streams/v1/image_metadata.sjson", -1)
   365  	r := bytes.NewReader([]byte(rawUnsignedIndex))
   366  	signedData, err := simplestreams.Encode(
   367  		r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	imageData["/signed/streams/v1/index.sjson"] = string(signedData)
   370  
   371  	// Replace the image id in the unsigned data with a different one so we can test that the right
   372  	// image id is used.
   373  	rawUnsignedProduct := strings.Replace(
   374  		unsignedProduct, "ami-26745463", "ami-123456", -1)
   375  	r = bytes.NewReader([]byte(rawUnsignedProduct))
   376  	signedData, err = simplestreams.Encode(
   377  		r, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
   378  	c.Assert(err, jc.ErrorIsNil)
   379  	imageData["/signed/streams/v1/image_metadata.sjson"] = string(signedData)
   380  	sstesting.SetRoundTripperFiles(imageData, map[string]int{"test://unauth": http.StatusUnauthorized})
   381  	s.origKey = imagemetadata.SetSigningPublicKey(sstesting.SignedMetadataPublicKey)
   382  }
   383  
   384  func (s *signedSuite) TearDownSuite(c *gc.C) {
   385  	sstesting.SetRoundTripperFiles(nil, nil)
   386  	imagemetadata.SetSigningPublicKey(s.origKey)
   387  }
   388  
   389  func (s *signedSuite) TestSignedImageMetadata(c *gc.C) {
   390  	signedSource := simplestreams.NewURLSignedDataSource("test", "test://host/signed", sstesting.SignedMetadataPublicKey, utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true)
   391  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   392  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   393  		Series:    []string{"precise"},
   394  		Arches:    []string{"amd64"},
   395  	})
   396  	images, resolveInfo, err := imagemetadata.Fetch([]simplestreams.DataSource{signedSource}, imageConstraint)
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Assert(len(images), gc.Equals, 1)
   399  	c.Assert(images[0].Id, gc.Equals, "ami-123456")
   400  	c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   401  		Source:    "test",
   402  		Signed:    true,
   403  		IndexURL:  "test://host/signed/streams/v1/index.sjson",
   404  		MirrorURL: "",
   405  	})
   406  }
   407  
   408  func (s *signedSuite) TestSignedImageMetadataInvalidSignature(c *gc.C) {
   409  	signedSource := simplestreams.NewURLDataSource("test", "test://host/signed", utils.VerifySSLHostnames, simplestreams.DEFAULT_CLOUD_DATA, true)
   410  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   411  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   412  		Series:    []string{"precise"},
   413  		Arches:    []string{"amd64"},
   414  	})
   415  	imagemetadata.SetSigningPublicKey(s.origKey)
   416  	_, _, err := imagemetadata.Fetch([]simplestreams.DataSource{signedSource}, imageConstraint)
   417  	c.Assert(err, gc.ErrorMatches, "cannot read index data.*")
   418  }
   419  
   420  var unsignedIndex = `
   421  {
   422   "index": {
   423    "com.ubuntu.cloud:released:precise": {
   424     "updated": "Wed, 01 May 2013 13:31:26 +0000",
   425     "clouds": [
   426  	{
   427  	 "region": "us-east-1",
   428  	 "endpoint": "https://ec2.us-east-1.amazonaws.com"
   429  	}
   430     ],
   431     "cloudname": "aws",
   432     "datatype": "image-ids",
   433     "format": "products:1.0",
   434     "products": [
   435  	"com.ubuntu.cloud:server:12.04:amd64"
   436     ],
   437     "path": "streams/v1/image_metadata.json"
   438    }
   439   },
   440   "updated": "Wed, 01 May 2013 13:31:26 +0000",
   441   "format": "index:1.0"
   442  }
   443  `
   444  var unsignedProduct = `
   445  {
   446   "updated": "Wed, 01 May 2013 13:31:26 +0000",
   447   "content_id": "com.ubuntu.cloud:released:aws",
   448   "products": {
   449    "com.ubuntu.cloud:server:12.04:amd64": {
   450     "release": "precise",
   451     "version": "12.04",
   452     "arch": "amd64",
   453     "region": "us-east-1",
   454     "endpoint": "https://somewhere",
   455     "versions": {
   456      "20121218": {
   457       "region": "us-east-1",
   458       "endpoint": "https://somewhere-else",
   459       "items": {
   460        "usww1pe": {
   461         "root_store": "ebs",
   462         "virt": "pv",
   463         "id": "ami-26745463"
   464        }
   465       },
   466       "pubname": "ubuntu-precise-12.04-amd64-server-20121218",
   467       "label": "release"
   468      }
   469     }
   470    }
   471   },
   472   "format": "products:1.0"
   473  }
   474  `