launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/juju/upgradejuju_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"archive/tar"
     8  	"bytes"
     9  	"compress/gzip"
    10  	"io"
    11  	"io/ioutil"
    12  	"strings"
    13  
    14  	gc "launchpad.net/gocheck"
    15  
    16  	"launchpad.net/juju-core/environs/filestorage"
    17  	"launchpad.net/juju-core/environs/storage"
    18  	"launchpad.net/juju-core/environs/sync"
    19  	envtesting "launchpad.net/juju-core/environs/testing"
    20  	envtools "launchpad.net/juju-core/environs/tools"
    21  	"launchpad.net/juju-core/juju/testing"
    22  	coretesting "launchpad.net/juju-core/testing"
    23  	jc "launchpad.net/juju-core/testing/checkers"
    24  	coretools "launchpad.net/juju-core/tools"
    25  	"launchpad.net/juju-core/version"
    26  )
    27  
    28  type UpgradeJujuSuite struct {
    29  	testing.JujuConnSuite
    30  }
    31  
    32  var _ = gc.Suite(&UpgradeJujuSuite{})
    33  
    34  var upgradeJujuTests = []struct {
    35  	about          string
    36  	tools          []string
    37  	currentVersion string
    38  	agentVersion   string
    39  
    40  	args           []string
    41  	expectInitErr  string
    42  	expectErr      string
    43  	expectVersion  string
    44  	expectUploaded []string
    45  }{{
    46  	about:          "unwanted extra argument",
    47  	currentVersion: "1.0.0-quantal-amd64",
    48  	args:           []string{"foo"},
    49  	expectInitErr:  "unrecognized args:.*",
    50  }, {
    51  	about:          "removed arg --dev specified",
    52  	currentVersion: "1.0.0-quantal-amd64",
    53  	args:           []string{"--dev"},
    54  	expectInitErr:  "flag provided but not defined: --dev",
    55  }, {
    56  	about:          "invalid --version value",
    57  	currentVersion: "1.0.0-quantal-amd64",
    58  	args:           []string{"--version", "invalid-version"},
    59  	expectInitErr:  "invalid version .*",
    60  }, {
    61  	about:          "just major version, no minor specified",
    62  	currentVersion: "4.2.0-quantal-amd64",
    63  	args:           []string{"--version", "4"},
    64  	expectInitErr:  `invalid version "4"`,
    65  }, {
    66  	about:          "major version upgrade to incompatible version",
    67  	currentVersion: "2.0.0-quantal-amd64",
    68  	args:           []string{"--version", "5.2.0"},
    69  	expectInitErr:  "cannot upgrade to version incompatible with CLI",
    70  }, {
    71  	about:          "major version downgrade to incompatible version",
    72  	currentVersion: "4.2.0-quantal-amd64",
    73  	args:           []string{"--version", "3.2.0"},
    74  	expectInitErr:  "cannot upgrade to version incompatible with CLI",
    75  }, {
    76  	about:          "invalid --series",
    77  	currentVersion: "4.2.0-quantal-amd64",
    78  	args:           []string{"--series", "precise&quantal"},
    79  	expectInitErr:  `invalid value "precise&quantal" for flag --series: .*`,
    80  }, {
    81  	about:          "--series without --upload-tools",
    82  	currentVersion: "4.2.0-quantal-amd64",
    83  	args:           []string{"--series", "precise,quantal"},
    84  	expectInitErr:  "--series requires --upload-tools",
    85  }, {
    86  	about:          "--upload-tools with inappropriate version 1",
    87  	currentVersion: "4.2.0-quantal-amd64",
    88  	args:           []string{"--upload-tools", "--version", "3.1.0"},
    89  	expectInitErr:  "cannot upgrade to version incompatible with CLI",
    90  }, {
    91  	about:          "--upload-tools with inappropriate version 2",
    92  	currentVersion: "3.2.7-quantal-amd64",
    93  	args:           []string{"--upload-tools", "--version", "3.1.0.4"},
    94  	expectInitErr:  "cannot specify build number when uploading tools",
    95  }, {
    96  	about:          "latest supported stable release",
    97  	tools:          []string{"2.2.0-quantal-amd64", "2.2.2-quantal-i386", "2.2.3-quantal-amd64"},
    98  	currentVersion: "2.0.0-quantal-amd64",
    99  	agentVersion:   "2.0.0",
   100  	expectVersion:  "2.2.3",
   101  }, {
   102  	about:          "latest current release",
   103  	tools:          []string{"2.0.5-quantal-amd64", "2.0.1-quantal-i386", "2.3.3-quantal-amd64"},
   104  	currentVersion: "2.0.0-quantal-amd64",
   105  	agentVersion:   "2.0.0",
   106  	expectVersion:  "2.0.5",
   107  }, {
   108  	about:          "no next supported available",
   109  	tools:          []string{"2.1.0-quantal-amd64", "2.1.5-quantal-i386", "2.3.3-quantal-amd64"},
   110  	currentVersion: "2.0.0-quantal-amd64",
   111  	agentVersion:   "2.0.0",
   112  	expectErr:      "no more recent supported versions available",
   113  }, {
   114  	about:          "latest supported stable, when client is dev",
   115  	tools:          []string{"2.1.1-quantal-amd64", "2.2.0-quantal-amd64", "2.3.0-quantal-amd64", "3.0.1-quantal-amd64"},
   116  	currentVersion: "2.1.0-quantal-amd64",
   117  	agentVersion:   "2.0.0",
   118  	expectVersion:  "2.2.0",
   119  }, {
   120  	about:          "latest current, when agent is dev",
   121  	tools:          []string{"2.1.1-quantal-amd64", "2.2.0-quantal-amd64", "2.3.0-quantal-amd64", "3.0.1-quantal-amd64"},
   122  	currentVersion: "2.0.0-quantal-amd64",
   123  	agentVersion:   "2.1.0",
   124  	expectVersion:  "2.2.0",
   125  }, {
   126  	about:          "specified version",
   127  	tools:          []string{"2.3.0-quantal-amd64"},
   128  	currentVersion: "2.0.0-quantal-amd64",
   129  	agentVersion:   "2.0.0",
   130  	args:           []string{"--version", "2.3.0"},
   131  	expectVersion:  "2.3.0",
   132  }, {
   133  	about:          "specified version missing, but already set",
   134  	currentVersion: "3.0.0-quantal-amd64",
   135  	agentVersion:   "3.0.0",
   136  	args:           []string{"--version", "3.0.0"},
   137  	expectVersion:  "3.0.0",
   138  }, {
   139  	about:          "specified version, no tools",
   140  	currentVersion: "3.0.0-quantal-amd64",
   141  	agentVersion:   "3.0.0",
   142  	args:           []string{"--version", "3.2.0"},
   143  	expectErr:      "no matching tools available",
   144  }, {
   145  	about:          "specified version, no matching major version",
   146  	tools:          []string{"4.2.0-quantal-amd64"},
   147  	currentVersion: "3.0.0-quantal-amd64",
   148  	agentVersion:   "3.0.0",
   149  	args:           []string{"--version", "3.2.0"},
   150  	expectErr:      "no matching tools available",
   151  }, {
   152  	about:          "specified version, no matching minor version",
   153  	tools:          []string{"3.4.0-quantal-amd64"},
   154  	currentVersion: "3.0.0-quantal-amd64",
   155  	agentVersion:   "3.0.0",
   156  	args:           []string{"--version", "3.2.0"},
   157  	expectErr:      "no matching tools available",
   158  }, {
   159  	about:          "specified version, no matching patch version",
   160  	tools:          []string{"3.2.5-quantal-amd64"},
   161  	currentVersion: "3.0.0-quantal-amd64",
   162  	agentVersion:   "3.0.0",
   163  	args:           []string{"--version", "3.2.0"},
   164  	expectErr:      "no matching tools available",
   165  }, {
   166  	about:          "specified version, no matching build version",
   167  	tools:          []string{"3.2.0.2-quantal-amd64"},
   168  	currentVersion: "3.0.0-quantal-amd64",
   169  	agentVersion:   "3.0.0",
   170  	args:           []string{"--version", "3.2.0"},
   171  	expectErr:      "no matching tools available",
   172  }, {
   173  	about:          "major version downgrade to incompatible version",
   174  	tools:          []string{"3.2.0-quantal-amd64"},
   175  	currentVersion: "3.2.0-quantal-amd64",
   176  	agentVersion:   "4.2.0",
   177  	args:           []string{"--version", "3.2.0"},
   178  	expectErr:      "cannot change major version from 4 to 3",
   179  }, {
   180  	about:          "major version upgrade to compatible version",
   181  	tools:          []string{"3.2.0-quantal-amd64"},
   182  	currentVersion: "3.2.0-quantal-amd64",
   183  	agentVersion:   "2.8.2",
   184  	args:           []string{"--version", "3.2.0"},
   185  	expectErr:      "major version upgrades are not supported yet",
   186  }, {
   187  	about:          "nothing available",
   188  	currentVersion: "2.0.0-quantal-amd64",
   189  	agentVersion:   "2.0.0",
   190  	expectVersion:  "2.0.0",
   191  }, {
   192  	about:          "nothing available 2",
   193  	currentVersion: "2.0.0-quantal-amd64",
   194  	tools:          []string{"3.2.0-quantal-amd64"},
   195  	agentVersion:   "2.0.0",
   196  	expectVersion:  "2.0.0",
   197  }, {
   198  	about:          "upload with default series",
   199  	currentVersion: "2.2.0-quantal-amd64",
   200  	agentVersion:   "2.0.0",
   201  	args:           []string{"--upload-tools"},
   202  	expectVersion:  "2.2.0.1",
   203  	expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-precise-amd64", "2.2.0.1-raring-amd64"},
   204  }, {
   205  	about:          "upload with explicit version",
   206  	currentVersion: "2.2.0-quantal-amd64",
   207  	agentVersion:   "2.0.0",
   208  	args:           []string{"--upload-tools", "--version", "2.7.3"},
   209  	expectVersion:  "2.7.3.1",
   210  	expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-precise-amd64", "2.7.3.1-raring-amd64"},
   211  }, {
   212  	about:          "upload with explicit series",
   213  	currentVersion: "2.2.0-quantal-amd64",
   214  	agentVersion:   "2.0.0",
   215  	args:           []string{"--upload-tools", "--series", "raring"},
   216  	expectVersion:  "2.2.0.1",
   217  	expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-raring-amd64"},
   218  }, {
   219  	about:          "upload dev version, currently on release version",
   220  	currentVersion: "2.1.0-quantal-amd64",
   221  	agentVersion:   "2.0.0",
   222  	args:           []string{"--upload-tools"},
   223  	expectVersion:  "2.1.0.1",
   224  	expectUploaded: []string{"2.1.0.1-quantal-amd64", "2.1.0.1-precise-amd64", "2.1.0.1-raring-amd64"},
   225  }, {
   226  	about:          "upload bumps version when necessary",
   227  	tools:          []string{"2.4.6-quantal-amd64", "2.4.8-quantal-amd64"},
   228  	currentVersion: "2.4.6-quantal-amd64",
   229  	agentVersion:   "2.4.0",
   230  	args:           []string{"--upload-tools"},
   231  	expectVersion:  "2.4.6.1",
   232  	expectUploaded: []string{"2.4.6.1-quantal-amd64", "2.4.6.1-precise-amd64", "2.4.6.1-raring-amd64"},
   233  }, {
   234  	about:          "upload re-bumps version when necessary",
   235  	tools:          []string{"2.4.6-quantal-amd64", "2.4.6.2-saucy-i386", "2.4.8-quantal-amd64"},
   236  	currentVersion: "2.4.6-quantal-amd64",
   237  	agentVersion:   "2.4.6.2",
   238  	args:           []string{"--upload-tools"},
   239  	expectVersion:  "2.4.6.3",
   240  	expectUploaded: []string{"2.4.6.3-quantal-amd64", "2.4.6.3-precise-amd64", "2.4.6.3-raring-amd64"},
   241  }, {
   242  	about:          "upload with explicit version bumps when necessary",
   243  	currentVersion: "2.2.0-quantal-amd64",
   244  	tools:          []string{"2.7.3.1-quantal-amd64"},
   245  	agentVersion:   "2.0.0",
   246  	args:           []string{"--upload-tools", "--version", "2.7.3"},
   247  	expectVersion:  "2.7.3.2",
   248  	expectUploaded: []string{"2.7.3.2-quantal-amd64", "2.7.3.2-precise-amd64", "2.7.3.2-raring-amd64"},
   249  }}
   250  
   251  // mockUploadTools simulates the effect of tools.Upload, but skips the time-
   252  // consuming build from source.
   253  // TODO(fwereade) better factor agent/tools such that build logic is
   254  // exposed and can itself be neatly mocked?
   255  func mockUploadTools(stor storage.Storage, forceVersion *version.Number, series ...string) (*coretools.Tools, error) {
   256  	vers := version.Current
   257  	if forceVersion != nil {
   258  		vers.Number = *forceVersion
   259  	}
   260  	versions := []version.Binary{vers}
   261  	for _, series := range series {
   262  		if series != version.Current.Series {
   263  			newVers := vers
   264  			newVers.Series = series
   265  			versions = append(versions, newVers)
   266  		}
   267  	}
   268  	agentTools, err := envtesting.UploadFakeToolsVersions(stor, versions...)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	return agentTools[0], nil
   273  }
   274  
   275  func (s *UpgradeJujuSuite) TestUpgradeJuju(c *gc.C) {
   276  	s.PatchValue(&sync.Upload, mockUploadTools)
   277  	oldVersion := version.Current
   278  	defer func() {
   279  		version.Current = oldVersion
   280  	}()
   281  
   282  	for i, test := range upgradeJujuTests {
   283  		c.Logf("\ntest %d: %s", i, test.about)
   284  		s.Reset(c)
   285  
   286  		// Set up apparent CLI version and initialize the command.
   287  		version.Current = version.MustParseBinary(test.currentVersion)
   288  		com := &UpgradeJujuCommand{}
   289  		if err := coretesting.InitCommand(com, test.args); err != nil {
   290  			if test.expectInitErr != "" {
   291  				c.Check(err, gc.ErrorMatches, test.expectInitErr)
   292  			} else {
   293  				c.Check(err, gc.IsNil)
   294  			}
   295  			continue
   296  		}
   297  
   298  		// Set up state and environ, and run the command.
   299  		oldcfg, err := s.State.EnvironConfig()
   300  		c.Assert(err, gc.IsNil)
   301  		toolsDir := c.MkDir()
   302  		cfg, err := oldcfg.Apply(map[string]interface{}{
   303  			"agent-version":      test.agentVersion,
   304  			"tools-metadata-url": "file://" + toolsDir,
   305  		})
   306  		c.Assert(err, gc.IsNil)
   307  		err = s.State.SetEnvironConfig(cfg, oldcfg)
   308  		c.Assert(err, gc.IsNil)
   309  		versions := make([]version.Binary, len(test.tools))
   310  		for i, v := range test.tools {
   311  			versions[i] = version.MustParseBinary(v)
   312  
   313  		}
   314  		envtesting.MustUploadFakeToolsVersions(s.Conn.Environ.Storage(), versions...)
   315  		stor, err := filestorage.NewFileStorageWriter(toolsDir, "")
   316  		c.Assert(err, gc.IsNil)
   317  		envtesting.MustUploadFakeToolsVersions(stor, versions...)
   318  		err = com.Run(coretesting.Context(c))
   319  		if test.expectErr != "" {
   320  			c.Check(err, gc.ErrorMatches, test.expectErr)
   321  			continue
   322  		} else if !c.Check(err, gc.IsNil) {
   323  			continue
   324  		}
   325  
   326  		// Check expected changes to environ/state.
   327  		cfg, err = s.State.EnvironConfig()
   328  		c.Check(err, gc.IsNil)
   329  		agentVersion, ok := cfg.AgentVersion()
   330  		c.Check(ok, gc.Equals, true)
   331  		c.Check(agentVersion, gc.Equals, version.MustParse(test.expectVersion))
   332  
   333  		for _, uploaded := range test.expectUploaded {
   334  			vers := version.MustParseBinary(uploaded)
   335  			r, err := storage.Get(s.Conn.Environ.Storage(), envtools.StorageName(vers))
   336  			if !c.Check(err, gc.IsNil) {
   337  				continue
   338  			}
   339  			data, err := ioutil.ReadAll(r)
   340  			r.Close()
   341  			c.Check(err, gc.IsNil)
   342  			checkToolsContent(c, data, "jujud contents "+uploaded)
   343  		}
   344  	}
   345  }
   346  
   347  func checkToolsContent(c *gc.C, data []byte, uploaded string) {
   348  	zr, err := gzip.NewReader(bytes.NewReader(data))
   349  	c.Check(err, gc.IsNil)
   350  	defer zr.Close()
   351  	tr := tar.NewReader(zr)
   352  	found := false
   353  	for {
   354  		hdr, err := tr.Next()
   355  		if err == io.EOF {
   356  			break
   357  		}
   358  		c.Check(err, gc.IsNil)
   359  		if strings.ContainsAny(hdr.Name, "/\\") {
   360  			c.Fail()
   361  		}
   362  		if hdr.Typeflag != tar.TypeReg {
   363  			c.Fail()
   364  		}
   365  		content, err := ioutil.ReadAll(tr)
   366  		c.Check(err, gc.IsNil)
   367  		c.Check(string(content), gc.Equals, uploaded)
   368  		found = true
   369  	}
   370  	c.Check(found, jc.IsTrue)
   371  }
   372  
   373  // JujuConnSuite very helpfully uploads some default
   374  // tools to the environment's storage. We don't want
   375  // 'em there; but we do want a consistent default-series
   376  // in the environment state.
   377  func (s *UpgradeJujuSuite) Reset(c *gc.C) {
   378  	s.JujuConnSuite.Reset(c)
   379  	envtesting.RemoveTools(c, s.Conn.Environ.Storage())
   380  	oldcfg, err := s.State.EnvironConfig()
   381  	c.Assert(err, gc.IsNil)
   382  	cfg, err := oldcfg.Apply(map[string]interface{}{
   383  		"default-series": "raring",
   384  		"agent-version":  "1.2.3",
   385  	})
   386  	c.Assert(err, gc.IsNil)
   387  	err = s.State.SetEnvironConfig(cfg, oldcfg)
   388  	c.Assert(err, gc.IsNil)
   389  }
   390  
   391  func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealUpload(c *gc.C) {
   392  	s.Reset(c)
   393  	_, err := coretesting.RunCommand(c, &UpgradeJujuCommand{}, []string{"--upload-tools"})
   394  	c.Assert(err, gc.IsNil)
   395  	vers := version.Current
   396  	vers.Build = 1
   397  	tools, err := envtools.FindInstanceTools(s.Conn.Environ, vers.Number, vers.Series, &vers.Arch)
   398  	c.Assert(err, gc.IsNil)
   399  	c.Assert(len(tools), gc.Equals, 1)
   400  }