github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/environs/tools/tools_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package tools_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	"github.com/juju/version"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/bootstrap"
    20  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    21  	envtesting "github.com/juju/juju/environs/testing"
    22  	envtools "github.com/juju/juju/environs/tools"
    23  	toolstesting "github.com/juju/juju/environs/tools/testing"
    24  	"github.com/juju/juju/juju/keys"
    25  	"github.com/juju/juju/jujuclient"
    26  	"github.com/juju/juju/provider/dummy"
    27  	coretesting "github.com/juju/juju/testing"
    28  	coretools "github.com/juju/juju/tools"
    29  	jujuversion "github.com/juju/juju/version"
    30  )
    31  
    32  type SimpleStreamsToolsSuite struct {
    33  	env environs.Environ
    34  	coretesting.BaseSuite
    35  	envtesting.ToolsFixture
    36  	origCurrentVersion version.Number
    37  	customToolsDir     string
    38  	publicToolsDir     string
    39  }
    40  
    41  func setupToolsTests() {
    42  	gc.Suite(&SimpleStreamsToolsSuite{})
    43  	gc.Suite(&ToolsListSuite{})
    44  }
    45  
    46  func (s *SimpleStreamsToolsSuite) SetUpSuite(c *gc.C) {
    47  	s.BaseSuite.SetUpSuite(c)
    48  	s.customToolsDir = c.MkDir()
    49  	s.publicToolsDir = c.MkDir()
    50  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
    51  }
    52  
    53  func (s *SimpleStreamsToolsSuite) SetUpTest(c *gc.C) {
    54  	s.ToolsFixture.DefaultBaseURL = utils.MakeFileURL(s.publicToolsDir)
    55  	s.BaseSuite.SetUpTest(c)
    56  	s.ToolsFixture.SetUpTest(c)
    57  	s.origCurrentVersion = jujuversion.Current
    58  	s.reset(c, nil)
    59  }
    60  
    61  func (s *SimpleStreamsToolsSuite) TearDownTest(c *gc.C) {
    62  	dummy.Reset(c)
    63  	jujuversion.Current = s.origCurrentVersion
    64  	s.ToolsFixture.TearDownTest(c)
    65  	s.BaseSuite.TearDownTest(c)
    66  }
    67  
    68  func (s *SimpleStreamsToolsSuite) reset(c *gc.C, attrs map[string]interface{}) {
    69  	final := map[string]interface{}{
    70  		"agent-metadata-url": utils.MakeFileURL(s.customToolsDir),
    71  		"agent-stream":       "proposed",
    72  	}
    73  	for k, v := range attrs {
    74  		final[k] = v
    75  	}
    76  	s.resetEnv(c, final)
    77  }
    78  
    79  func (s *SimpleStreamsToolsSuite) removeTools(c *gc.C) {
    80  	for _, dir := range []string{s.customToolsDir, s.publicToolsDir} {
    81  		files, err := ioutil.ReadDir(dir)
    82  		c.Assert(err, jc.ErrorIsNil)
    83  		for _, f := range files {
    84  			err := os.RemoveAll(filepath.Join(dir, f.Name()))
    85  			c.Assert(err, jc.ErrorIsNil)
    86  		}
    87  	}
    88  }
    89  
    90  func (s *SimpleStreamsToolsSuite) uploadCustom(c *gc.C, verses ...version.Binary) map[version.Binary]string {
    91  	return toolstesting.UploadToDirectory(c, s.customToolsDir, toolstesting.StreamVersions{"proposed": verses})["proposed"]
    92  }
    93  
    94  func (s *SimpleStreamsToolsSuite) uploadPublic(c *gc.C, verses ...version.Binary) map[version.Binary]string {
    95  	return toolstesting.UploadToDirectory(c, s.publicToolsDir, toolstesting.StreamVersions{"proposed": verses})["proposed"]
    96  }
    97  
    98  func (s *SimpleStreamsToolsSuite) uploadStreams(c *gc.C, versions toolstesting.StreamVersions) map[string]map[version.Binary]string {
    99  	return toolstesting.UploadToDirectory(c, s.publicToolsDir, versions)
   100  }
   101  
   102  func (s *SimpleStreamsToolsSuite) resetEnv(c *gc.C, attrs map[string]interface{}) {
   103  	jujuversion.Current = s.origCurrentVersion
   104  	dummy.Reset(c)
   105  	attrs = dummy.SampleConfig().Merge(attrs)
   106  	env, err := bootstrap.PrepareController(false, envtesting.BootstrapContext(c),
   107  		jujuclient.NewMemStore(),
   108  		bootstrap.PrepareParams{
   109  			ControllerConfig: coretesting.FakeControllerConfig(),
   110  			ControllerName:   attrs["name"].(string),
   111  			ModelConfig:      attrs,
   112  			Cloud:            dummy.SampleCloudSpec(),
   113  			AdminSecret:      "admin-secret",
   114  		},
   115  	)
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	s.env = env.(environs.Environ)
   118  	s.removeTools(c)
   119  }
   120  
   121  var findToolsTests = []struct {
   122  	info   string
   123  	major  int
   124  	minor  int
   125  	custom []version.Binary
   126  	public []version.Binary
   127  	expect []version.Binary
   128  	err    error
   129  }{{
   130  	info:  "none available anywhere",
   131  	major: 1,
   132  	err:   envtools.ErrNoTools,
   133  }, {
   134  	info:   "custom/private tools only, none matching",
   135  	major:  1,
   136  	minor:  2,
   137  	custom: envtesting.V220all,
   138  	err:    coretools.ErrNoMatches,
   139  }, {
   140  	info:   "custom tools found",
   141  	major:  1,
   142  	minor:  2,
   143  	custom: envtesting.VAll,
   144  	expect: envtesting.V120all,
   145  }, {
   146  	info:   "public tools found",
   147  	major:  1,
   148  	minor:  1,
   149  	public: envtesting.VAll,
   150  	expect: envtesting.V110all,
   151  }, {
   152  	info:   "public and custom tools found, only taken from custom",
   153  	major:  1,
   154  	minor:  1,
   155  	custom: envtesting.V110p,
   156  	public: envtesting.VAll,
   157  	expect: envtesting.V110p,
   158  }, {
   159  	info:   "custom tools completely block public ones",
   160  	major:  1,
   161  	minor:  -1,
   162  	custom: envtesting.V220all,
   163  	public: envtesting.VAll,
   164  	expect: envtesting.V1all,
   165  }, {
   166  	info:   "tools matching major version only",
   167  	major:  1,
   168  	minor:  -1,
   169  	public: envtesting.VAll,
   170  	expect: envtesting.V1all,
   171  }}
   172  
   173  func (s *SimpleStreamsToolsSuite) TestFindTools(c *gc.C) {
   174  	for i, test := range findToolsTests {
   175  		c.Logf("\ntest %d: %s", i, test.info)
   176  		s.reset(c, nil)
   177  		custom := s.uploadCustom(c, test.custom...)
   178  		public := s.uploadPublic(c, test.public...)
   179  		streams := envtools.PreferredStreams(&jujuversion.Current, s.env.Config().Development(), s.env.Config().AgentStream())
   180  		actual, err := envtools.FindTools(s.env, test.major, test.minor, streams, coretools.Filter{})
   181  		if test.err != nil {
   182  			if len(actual) > 0 {
   183  				c.Logf(actual.String())
   184  			}
   185  			c.Check(err, jc.Satisfies, errors.IsNotFound)
   186  			continue
   187  		}
   188  		expect := map[version.Binary][]string{}
   189  		for _, expected := range test.expect {
   190  			// If the tools exist in custom, that's preferred.
   191  			url, ok := custom[expected]
   192  			if !ok {
   193  				url = public[expected]
   194  			}
   195  			expect[expected] = append(expect[expected], url)
   196  		}
   197  		c.Check(actual.URLs(), gc.DeepEquals, expect)
   198  	}
   199  }
   200  
   201  func (s *SimpleStreamsToolsSuite) TestFindToolsFiltering(c *gc.C) {
   202  	var tw loggo.TestWriter
   203  	c.Assert(loggo.RegisterWriter("filter-tester", &tw), gc.IsNil)
   204  	defer loggo.RemoveWriter("filter-tester")
   205  	logger := loggo.GetLogger("juju.environs")
   206  	defer logger.SetLogLevel(logger.LogLevel())
   207  	logger.SetLogLevel(loggo.TRACE)
   208  
   209  	_, err := envtools.FindTools(
   210  		s.env, 1, -1, []string{"released"}, coretools.Filter{Number: version.Number{Major: 1, Minor: 2, Patch: 3}})
   211  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   212  	// This is slightly overly prescriptive, but feel free to change or add
   213  	// messages. This still helps to ensure that all log messages are
   214  	// properly formed.
   215  	messages := []jc.SimpleMessage{
   216  		{loggo.DEBUG, "reading agent binaries with major version 1"},
   217  		{loggo.DEBUG, "filtering agent binaries by version: \\d+\\.\\d+\\.\\d+"},
   218  		{loggo.TRACE, "no architecture specified when finding agent binaries, looking for "},
   219  		{loggo.TRACE, "no series specified when finding agent binaries, looking for \\[.*\\]"},
   220  	}
   221  	sources, err := envtools.GetMetadataSources(s.env)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	for i := 0; i < len(sources); i++ {
   224  		messages = append(messages,
   225  			jc.SimpleMessage{loggo.TRACE, `fetchData failed for .*`},
   226  			jc.SimpleMessage{loggo.TRACE, `cannot load index .*`})
   227  	}
   228  	c.Check(tw.Log(), jc.LogMatches, messages)
   229  }
   230  
   231  var findExactToolsTests = []struct {
   232  	info string
   233  	// These are the contents of the proposed streams in each source.
   234  	custom []version.Binary
   235  	public []version.Binary
   236  	seek   version.Binary
   237  	err    error
   238  }{{
   239  	info: "nothing available",
   240  	seek: envtesting.V100p64,
   241  	err:  envtools.ErrNoTools,
   242  }, {
   243  	info:   "only non-matches available in custom",
   244  	custom: append(envtesting.V110all, envtesting.V100p32, envtesting.V100q64, envtesting.V1001p64),
   245  	seek:   envtesting.V100p64,
   246  	err:    coretools.ErrNoMatches,
   247  }, {
   248  	info:   "exact match available in custom",
   249  	custom: []version.Binary{envtesting.V100p64},
   250  	seek:   envtesting.V100p64,
   251  }, {
   252  	info:   "only non-matches available in public",
   253  	custom: append(envtesting.V110all, envtesting.V100p32, envtesting.V100q64, envtesting.V1001p64),
   254  	seek:   envtesting.V100p64,
   255  	err:    coretools.ErrNoMatches,
   256  }, {
   257  	info:   "exact match available in public",
   258  	public: []version.Binary{envtesting.V100p64},
   259  	seek:   envtesting.V100p64,
   260  }, {
   261  	info:   "exact match in public not blocked by custom",
   262  	custom: envtesting.V110all,
   263  	public: []version.Binary{envtesting.V100p64},
   264  	seek:   envtesting.V100p64,
   265  }}
   266  
   267  func (s *SimpleStreamsToolsSuite) TestFindExactTools(c *gc.C) {
   268  	for i, test := range findExactToolsTests {
   269  		c.Logf("\ntest %d: %s", i, test.info)
   270  		s.reset(c, nil)
   271  		custom := s.uploadCustom(c, test.custom...)
   272  		public := s.uploadPublic(c, test.public...)
   273  		actual, err := envtools.FindExactTools(s.env, test.seek.Number, test.seek.Series, test.seek.Arch)
   274  		if test.err == nil {
   275  			if !c.Check(err, jc.ErrorIsNil) {
   276  				continue
   277  			}
   278  			c.Check(actual.Version, gc.Equals, test.seek)
   279  			if _, ok := custom[actual.Version]; ok {
   280  				c.Check(actual.URL, gc.DeepEquals, custom[actual.Version])
   281  			} else {
   282  				c.Check(actual.URL, gc.DeepEquals, public[actual.Version])
   283  			}
   284  		} else {
   285  			c.Check(err, jc.Satisfies, errors.IsNotFound)
   286  		}
   287  	}
   288  }
   289  
   290  func copyAndAppend(vs []version.Binary, more ...[]version.Binary) []version.Binary {
   291  	// TODO(babbageclunk): I think the append(someversions,
   292  	// moreversions...) technique used in environs/testing/tools.go
   293  	// might be wrong because it can mutate someversions if there's
   294  	// enough capacity. Use this there.
   295  	// https://medium.com/@Jarema./golang-slice-append-gotcha-e9020ff37374
   296  	result := make([]version.Binary, len(vs))
   297  	copy(result, vs)
   298  	for _, items := range more {
   299  		result = append(result, items...)
   300  	}
   301  	return result
   302  }
   303  
   304  var findToolsFallbackTests = []struct {
   305  	info     string
   306  	major    int
   307  	minor    int
   308  	streams  []string
   309  	devel    []version.Binary
   310  	proposed []version.Binary
   311  	released []version.Binary
   312  	expect   []version.Binary
   313  	err      error
   314  }{{
   315  	info:    "nothing available",
   316  	major:   1,
   317  	streams: []string{"released"},
   318  	err:     envtools.ErrNoTools,
   319  }, {
   320  	info:    "only available in non-selected stream",
   321  	major:   1,
   322  	minor:   2,
   323  	streams: []string{"released"},
   324  	devel:   envtesting.VAll,
   325  	err:     coretools.ErrNoMatches,
   326  }, {
   327  	info:     "finds things in devel and released, ignores proposed",
   328  	major:    1,
   329  	minor:    -1,
   330  	streams:  []string{"devel", "released"},
   331  	devel:    envtesting.V120all,
   332  	proposed: envtesting.V110all,
   333  	released: envtesting.V100all,
   334  	expect:   copyAndAppend(envtesting.V120all, envtesting.V100all),
   335  }, {
   336  	info:     "finds matching things everywhere",
   337  	major:    1,
   338  	minor:    2,
   339  	streams:  []string{"devel", "proposed", "released"},
   340  	devel:    []version.Binary{envtesting.V110q64, envtesting.V120q64},
   341  	proposed: []version.Binary{envtesting.V110p64, envtesting.V120p64},
   342  	released: []version.Binary{envtesting.V100p64, envtesting.V120t64},
   343  	expect:   []version.Binary{envtesting.V120p64, envtesting.V120q64, envtesting.V120t64},
   344  }}
   345  
   346  func (s *SimpleStreamsToolsSuite) TestFindToolsWithStreamFallback(c *gc.C) {
   347  	for i, test := range findToolsFallbackTests {
   348  		c.Logf("\ntest %d: %s", i, test.info)
   349  		s.reset(c, nil)
   350  		streams := s.uploadStreams(c, toolstesting.StreamVersions{
   351  			"devel":    test.devel,
   352  			"proposed": test.proposed,
   353  			"released": test.released,
   354  		})
   355  		actual, err := envtools.FindTools(s.env, test.major, test.minor, test.streams, coretools.Filter{})
   356  		if test.err != nil {
   357  			if len(actual) > 0 {
   358  				c.Logf(actual.String())
   359  			}
   360  			c.Check(err, jc.Satisfies, errors.IsNotFound)
   361  			continue
   362  		}
   363  		expect := map[version.Binary][]string{}
   364  		for _, expected := range test.expect {
   365  			for _, stream := range []string{"devel", "proposed", "released"} {
   366  				if url, ok := streams[stream][expected]; ok {
   367  					expect[expected] = []string{url}
   368  					break
   369  				}
   370  			}
   371  		}
   372  		c.Check(actual.URLs(), gc.DeepEquals, expect)
   373  	}
   374  }
   375  
   376  var preferredStreamTests = []struct {
   377  	explicitVers   string
   378  	currentVers    string
   379  	forceDevel     bool
   380  	streamInConfig string
   381  	expected       []string
   382  }{{
   383  	currentVers:    "1.22.0",
   384  	streamInConfig: "released",
   385  	expected:       []string{"released"},
   386  }, {
   387  	currentVers:    "1.22.0",
   388  	streamInConfig: "proposed",
   389  	expected:       []string{"proposed", "released"},
   390  }, {
   391  	currentVers:    "1.22.0",
   392  	streamInConfig: "devel",
   393  	expected:       []string{"devel", "proposed", "released"},
   394  }, {
   395  	currentVers:    "1.22.0",
   396  	streamInConfig: "testing",
   397  	expected:       []string{"testing", "devel", "proposed", "released"},
   398  }, {
   399  	currentVers: "1.22.0",
   400  	expected:    []string{"released"},
   401  }, {
   402  	currentVers: "1.22-beta1",
   403  	expected:    []string{"devel", "proposed", "released"},
   404  }, {
   405  	currentVers:    "1.22-beta1",
   406  	streamInConfig: "released",
   407  	expected:       []string{"devel", "proposed", "released"},
   408  }, {
   409  	currentVers:    "1.22-beta1",
   410  	streamInConfig: "devel",
   411  	expected:       []string{"devel", "proposed", "released"},
   412  }, {
   413  	currentVers: "1.22.0",
   414  	forceDevel:  true,
   415  	expected:    []string{"devel", "proposed", "released"},
   416  }, {
   417  	currentVers:  "1.22.0",
   418  	explicitVers: "1.22-beta1",
   419  	expected:     []string{"devel", "proposed", "released"},
   420  }, {
   421  	currentVers:  "1.22-bta1",
   422  	explicitVers: "1.22.0",
   423  	expected:     []string{"released"},
   424  }}
   425  
   426  func (s *SimpleStreamsToolsSuite) TestPreferredStreams(c *gc.C) {
   427  	for i, test := range preferredStreamTests {
   428  		c.Logf("\ntest %d", i)
   429  		s.PatchValue(&jujuversion.Current, version.MustParse(test.currentVers))
   430  		var vers *version.Number
   431  		if test.explicitVers != "" {
   432  			v := version.MustParse(test.explicitVers)
   433  			vers = &v
   434  		}
   435  		obtained := envtools.PreferredStreams(vers, test.forceDevel, test.streamInConfig)
   436  		c.Check(obtained, gc.DeepEquals, test.expected)
   437  	}
   438  }
   439  
   440  // fakeToolsForSeries fakes a Tools object with just enough information for
   441  // testing the handling its OS series.
   442  func fakeToolsForSeries(series string) *coretools.Tools {
   443  	return &coretools.Tools{Version: version.Binary{Series: series}}
   444  }
   445  
   446  // fakeToolsList fakes a envtools.List containing Tools objects for the given
   447  // respective series, in the same number and order.
   448  func fakeToolsList(series ...string) coretools.List {
   449  	list := coretools.List{}
   450  	for _, name := range series {
   451  		list = append(list, fakeToolsForSeries(name))
   452  	}
   453  	return list
   454  }
   455  
   456  type ToolsListSuite struct{}
   457  
   458  func (s *ToolsListSuite) TestCheckToolsSeriesRequiresTools(c *gc.C) {
   459  	err := envtools.CheckToolsSeries(fakeToolsList(), "precise")
   460  	c.Assert(err, gc.NotNil)
   461  	c.Check(err, gc.ErrorMatches, "expected single series, got \\[\\]")
   462  }
   463  
   464  func (s *ToolsListSuite) TestCheckToolsSeriesAcceptsOneSetOfTools(c *gc.C) {
   465  	names := []string{"precise", "raring"}
   466  	for _, series := range names {
   467  		list := fakeToolsList(series)
   468  		err := envtools.CheckToolsSeries(list, series)
   469  		c.Check(err, jc.ErrorIsNil)
   470  	}
   471  }
   472  
   473  func (s *ToolsListSuite) TestCheckToolsSeriesAcceptsMultipleForSameSeries(c *gc.C) {
   474  	series := "quantal"
   475  	list := fakeToolsList(series, series, series)
   476  	err := envtools.CheckToolsSeries(list, series)
   477  	c.Check(err, jc.ErrorIsNil)
   478  }
   479  
   480  func (s *ToolsListSuite) TestCheckToolsSeriesRejectsToolsForOtherSeries(c *gc.C) {
   481  	list := fakeToolsList("hoary")
   482  	err := envtools.CheckToolsSeries(list, "warty")
   483  	c.Assert(err, gc.NotNil)
   484  	c.Check(err, gc.ErrorMatches, "agent binary mismatch: expected series warty, got hoary")
   485  }
   486  
   487  func (s *ToolsListSuite) TestCheckToolsSeriesRejectsToolsForMixedSeries(c *gc.C) {
   488  	list := fakeToolsList("precise", "raring")
   489  	err := envtools.CheckToolsSeries(list, "precise")
   490  	c.Assert(err, gc.NotNil)
   491  	c.Check(err, gc.ErrorMatches, "expected single series, got .*")
   492  }