github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/commands/upgradejuju_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"archive/tar"
     8  	"bytes"
     9  	"compress/gzip"
    10  	"io"
    11  	"io/ioutil"
    12  	"strings"
    13  
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/arch"
    16  	"github.com/juju/utils/series"
    17  	"github.com/juju/version"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/apiserver/common"
    21  	"github.com/juju/juju/apiserver/params"
    22  	apiservertesting "github.com/juju/juju/apiserver/testing"
    23  	cmdcommon "github.com/juju/juju/cmd/juju/common"
    24  	"github.com/juju/juju/cmd/modelcmd"
    25  	"github.com/juju/juju/environs/config"
    26  	"github.com/juju/juju/environs/filestorage"
    27  	"github.com/juju/juju/environs/sync"
    28  	envtesting "github.com/juju/juju/environs/testing"
    29  	"github.com/juju/juju/environs/tools"
    30  	toolstesting "github.com/juju/juju/environs/tools/testing"
    31  	jujutesting "github.com/juju/juju/juju/testing"
    32  	"github.com/juju/juju/network"
    33  	_ "github.com/juju/juju/provider/dummy"
    34  	"github.com/juju/juju/state"
    35  	coretesting "github.com/juju/juju/testing"
    36  	coretools "github.com/juju/juju/tools"
    37  	jujuversion "github.com/juju/juju/version"
    38  )
    39  
    40  type UpgradeJujuSuite struct {
    41  	jujutesting.JujuConnSuite
    42  
    43  	resources  *common.Resources
    44  	authoriser apiservertesting.FakeAuthorizer
    45  
    46  	toolsDir string
    47  	cmdcommon.CmdBlockHelper
    48  }
    49  
    50  func (s *UpgradeJujuSuite) SetUpTest(c *gc.C) {
    51  	s.JujuConnSuite.SetUpTest(c)
    52  	s.resources = common.NewResources()
    53  	s.authoriser = apiservertesting.FakeAuthorizer{
    54  		Tag: s.AdminUserTag(c),
    55  	}
    56  
    57  	s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState)
    58  	c.Assert(s.CmdBlockHelper, gc.NotNil)
    59  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
    60  }
    61  
    62  var _ = gc.Suite(&UpgradeJujuSuite{})
    63  
    64  var upgradeJujuTests = []struct {
    65  	about          string
    66  	tools          []string
    67  	currentVersion string
    68  	agentVersion   string
    69  
    70  	args           []string
    71  	expectInitErr  string
    72  	expectErr      string
    73  	expectVersion  string
    74  	expectUploaded []string
    75  	upgradeMap     map[int]version.Number
    76  }{{
    77  	about:          "unwanted extra argument",
    78  	currentVersion: "1.0.0-quantal-amd64",
    79  	args:           []string{"foo"},
    80  	expectInitErr:  "unrecognized args:.*",
    81  }, {
    82  	about:          "removed arg --dev specified",
    83  	currentVersion: "1.0.0-quantal-amd64",
    84  	args:           []string{"--dev"},
    85  	expectInitErr:  "flag provided but not defined: --dev",
    86  }, {
    87  	about:          "invalid --version value",
    88  	currentVersion: "1.0.0-quantal-amd64",
    89  	args:           []string{"--version", "invalid-version"},
    90  	expectInitErr:  "invalid version .*",
    91  }, {
    92  	about:          "just major version, no minor specified",
    93  	currentVersion: "4.2.0-quantal-amd64",
    94  	args:           []string{"--version", "4"},
    95  	expectInitErr:  `invalid version "4"`,
    96  }, {
    97  	about:          "major version upgrade to incompatible version",
    98  	currentVersion: "2.0.0-quantal-amd64",
    99  	agentVersion:   "2.0.0",
   100  	args:           []string{"--version", "5.2.0"},
   101  	expectErr:      `unknown version "5.2.0"`,
   102  }, {
   103  	about:          "major version downgrade to incompatible version",
   104  	currentVersion: "4.2.0-quantal-amd64",
   105  	agentVersion:   "4.2.0",
   106  	args:           []string{"--version", "3.2.0"},
   107  	expectErr:      "cannot change version from 4.2.0 to 3.2.0",
   108  }, {
   109  	about:          "--upload-tools with inappropriate version 1",
   110  	currentVersion: "4.2.0-quantal-amd64",
   111  	agentVersion:   "4.2.0",
   112  	args:           []string{"--upload-tools", "--version", "3.1.0"},
   113  	expectErr:      "cannot change version from 4.2.0 to 3.1.0",
   114  }, {
   115  	about:          "--upload-tools with inappropriate version 2",
   116  	currentVersion: "3.2.7-quantal-amd64",
   117  	args:           []string{"--upload-tools", "--version", "3.2.8.4"},
   118  	expectInitErr:  "cannot specify build number when uploading tools",
   119  }, {
   120  	about:          "latest supported stable release",
   121  	tools:          []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64"},
   122  	currentVersion: "2.0.0-quantal-amd64",
   123  	agentVersion:   "2.0.0",
   124  	expectVersion:  "2.1.3",
   125  }, {
   126  	about:          "latest current release",
   127  	tools:          []string{"2.0.5-quantal-amd64", "2.0.1-quantal-i386", "2.3.3-quantal-amd64"},
   128  	currentVersion: "2.0.0-quantal-amd64",
   129  	agentVersion:   "2.0.0",
   130  	expectVersion:  "2.0.5",
   131  }, {
   132  	about:          "latest current release matching CLI, major version, no matching major tools",
   133  	tools:          []string{"2.8.2-quantal-amd64"},
   134  	currentVersion: "3.0.2-quantal-amd64",
   135  	agentVersion:   "2.8.2",
   136  	expectVersion:  "2.8.2",
   137  }, {
   138  	about:          "latest current release matching CLI, major version, no matching tools",
   139  	tools:          []string{"3.3.0-quantal-amd64"},
   140  	currentVersion: "3.0.2-quantal-amd64",
   141  	agentVersion:   "2.8.2",
   142  	expectVersion:  "2.8.2",
   143  }, {
   144  	about:          "no next supported available",
   145  	tools:          []string{"2.2.0-quantal-amd64", "2.2.5-quantal-i386", "2.3.3-quantal-amd64", "2.1-dev1-quantal-amd64"},
   146  	currentVersion: "2.0.0-quantal-amd64",
   147  	agentVersion:   "2.0.0",
   148  	expectErr:      "no more recent supported versions available",
   149  }, {
   150  	about:          "latest supported stable, when client is dev",
   151  	tools:          []string{"2.1-dev1-quantal-amd64", "2.1.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"},
   152  	currentVersion: "2.1-dev0-quantal-amd64",
   153  	agentVersion:   "2.0.0",
   154  	expectVersion:  "2.1.0",
   155  }, {
   156  	about:          "latest current, when agent is dev",
   157  	tools:          []string{"2.1-dev1-quantal-amd64", "2.2.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"},
   158  	currentVersion: "2.0.0-quantal-amd64",
   159  	agentVersion:   "2.1-dev0",
   160  	expectVersion:  "2.2.0",
   161  }, {
   162  	about:          "specified version",
   163  	tools:          []string{"2.3-dev0-quantal-amd64"},
   164  	currentVersion: "2.0.0-quantal-amd64",
   165  	agentVersion:   "2.0.0",
   166  	args:           []string{"--version", "2.3-dev0"},
   167  	expectVersion:  "2.3-dev0",
   168  }, {
   169  	about:          "specified major version",
   170  	tools:          []string{"3.0.2-quantal-amd64"},
   171  	currentVersion: "3.0.2-quantal-amd64",
   172  	agentVersion:   "2.8.2",
   173  	args:           []string{"--version", "3.0.2"},
   174  	expectVersion:  "3.0.2",
   175  	upgradeMap:     map[int]version.Number{3: version.MustParse("2.8.2")},
   176  }, {
   177  	about:          "specified version missing, but already set",
   178  	currentVersion: "3.0.0-quantal-amd64",
   179  	agentVersion:   "3.0.0",
   180  	args:           []string{"--version", "3.0.0"},
   181  	expectVersion:  "3.0.0",
   182  }, {
   183  	about:          "specified version, no tools",
   184  	currentVersion: "3.0.0-quantal-amd64",
   185  	agentVersion:   "3.0.0",
   186  	args:           []string{"--version", "3.2.0"},
   187  	expectErr:      "no tools available",
   188  }, {
   189  	about:          "specified version, no matching major version",
   190  	tools:          []string{"4.2.0-quantal-amd64"},
   191  	currentVersion: "3.0.0-quantal-amd64",
   192  	agentVersion:   "3.0.0",
   193  	args:           []string{"--version", "3.2.0"},
   194  	expectErr:      "no matching tools available",
   195  }, {
   196  	about:          "specified version, no matching minor version",
   197  	tools:          []string{"3.4.0-quantal-amd64"},
   198  	currentVersion: "3.0.0-quantal-amd64",
   199  	agentVersion:   "3.0.0",
   200  	args:           []string{"--version", "3.2.0"},
   201  	expectErr:      "no matching tools available",
   202  }, {
   203  	about:          "specified version, no matching patch version",
   204  	tools:          []string{"3.2.5-quantal-amd64"},
   205  	currentVersion: "3.0.0-quantal-amd64",
   206  	agentVersion:   "3.0.0",
   207  	args:           []string{"--version", "3.2.0"},
   208  	expectErr:      "no matching tools available",
   209  }, {
   210  	about:          "specified version, no matching build version",
   211  	tools:          []string{"3.2.0.2-quantal-amd64"},
   212  	currentVersion: "3.0.0-quantal-amd64",
   213  	agentVersion:   "3.0.0",
   214  	args:           []string{"--version", "3.2.0"},
   215  	expectErr:      "no matching tools available",
   216  }, {
   217  	about:          "incompatible version (minor != 0)",
   218  	tools:          []string{"3.2.0-quantal-amd64"},
   219  	currentVersion: "4.2.0-quantal-amd64",
   220  	agentVersion:   "3.2.0",
   221  	args:           []string{"--version", "3.2.0"},
   222  	expectErr:      "cannot upgrade a 3.2.0 model with a 4.2.0 client",
   223  }, {
   224  	about:          "incompatible version (model major > client major)",
   225  	tools:          []string{"3.2.0-quantal-amd64"},
   226  	currentVersion: "3.2.0-quantal-amd64",
   227  	agentVersion:   "4.2.0",
   228  	args:           []string{"--version", "3.2.0"},
   229  	expectErr:      "cannot upgrade a 4.2.0 model with a 3.2.0 client",
   230  }, {
   231  	about:          "incompatible version (model major < client major - 1)",
   232  	tools:          []string{"3.2.0-quantal-amd64"},
   233  	currentVersion: "4.0.2-quantal-amd64",
   234  	agentVersion:   "2.0.0",
   235  	args:           []string{"--version", "3.2.0"},
   236  	expectErr:      "cannot upgrade a 2.0.0 model with a 4.0.2 client",
   237  }, {
   238  	about:          "minor version downgrade to incompatible version",
   239  	tools:          []string{"3.2.0-quantal-amd64"},
   240  	currentVersion: "3.2.0-quantal-amd64",
   241  	agentVersion:   "3.3-dev0",
   242  	args:           []string{"--version", "3.2.0"},
   243  	expectErr:      "cannot change version from 3.3-dev0 to 3.2.0",
   244  }, {
   245  	about:          "nothing available",
   246  	currentVersion: "2.0.0-quantal-amd64",
   247  	agentVersion:   "2.0.0",
   248  	expectVersion:  "2.0.0",
   249  }, {
   250  	about:          "nothing available 2",
   251  	currentVersion: "2.0.0-quantal-amd64",
   252  	tools:          []string{"3.2.0-quantal-amd64"},
   253  	agentVersion:   "2.0.0",
   254  	expectVersion:  "2.0.0",
   255  }, {
   256  	about:          "upload with default series",
   257  	currentVersion: "2.2.0-quantal-amd64",
   258  	agentVersion:   "2.0.0",
   259  	args:           []string{"--upload-tools"},
   260  	expectVersion:  "2.2.0.1",
   261  	expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-%LTS%-amd64", "2.2.0.1-raring-amd64"},
   262  }, {
   263  	about:          "upload with explicit version",
   264  	currentVersion: "2.2.0-quantal-amd64",
   265  	agentVersion:   "2.0.0",
   266  	args:           []string{"--upload-tools", "--version", "2.7.3"},
   267  	expectVersion:  "2.7.3.1",
   268  	expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-%LTS%-amd64", "2.7.3.1-raring-amd64"},
   269  }, {
   270  	about:          "upload dev version, currently on release version",
   271  	currentVersion: "2.1.0-quantal-amd64",
   272  	agentVersion:   "2.0.0",
   273  	args:           []string{"--upload-tools"},
   274  	expectVersion:  "2.1.0.1",
   275  	expectUploaded: []string{"2.1.0.1-quantal-amd64", "2.1.0.1-%LTS%-amd64", "2.1.0.1-raring-amd64"},
   276  }, {
   277  	about:          "upload bumps version when necessary",
   278  	tools:          []string{"2.4.6-quantal-amd64", "2.4.8-quantal-amd64"},
   279  	currentVersion: "2.4.6-quantal-amd64",
   280  	agentVersion:   "2.4.0",
   281  	args:           []string{"--upload-tools"},
   282  	expectVersion:  "2.4.6.1",
   283  	expectUploaded: []string{"2.4.6.1-quantal-amd64", "2.4.6.1-%LTS%-amd64", "2.4.6.1-raring-amd64"},
   284  }, {
   285  	about:          "upload re-bumps version when necessary",
   286  	tools:          []string{"2.4.6-quantal-amd64", "2.4.6.2-saucy-i386", "2.4.8-quantal-amd64"},
   287  	currentVersion: "2.4.6-quantal-amd64",
   288  	agentVersion:   "2.4.6.2",
   289  	args:           []string{"--upload-tools"},
   290  	expectVersion:  "2.4.6.3",
   291  	expectUploaded: []string{"2.4.6.3-quantal-amd64", "2.4.6.3-%LTS%-amd64", "2.4.6.3-raring-amd64"},
   292  }, {
   293  	about:          "upload with explicit version bumps when necessary",
   294  	currentVersion: "2.2.0-quantal-amd64",
   295  	tools:          []string{"2.7.3.1-quantal-amd64"},
   296  	agentVersion:   "2.0.0",
   297  	args:           []string{"--upload-tools", "--version", "2.7.3"},
   298  	expectVersion:  "2.7.3.2",
   299  	expectUploaded: []string{"2.7.3.2-quantal-amd64", "2.7.3.2-%LTS%-amd64", "2.7.3.2-raring-amd64"},
   300  }, {
   301  	about:          "latest supported stable release",
   302  	tools:          []string{"1.21.3-quantal-amd64", "1.22.1-quantal-amd64"},
   303  	currentVersion: "1.22.1-quantal-amd64",
   304  	agentVersion:   "1.20.14",
   305  	expectVersion:  "1.21.3",
   306  }}
   307  
   308  func (s *UpgradeJujuSuite) TestUpgradeJuju(c *gc.C) {
   309  	for i, test := range upgradeJujuTests {
   310  		c.Logf("\ntest %d: %s", i, test.about)
   311  		s.Reset(c)
   312  		tools.DefaultBaseURL = ""
   313  
   314  		// Set up apparent CLI version and initialize the command.
   315  		current := version.MustParseBinary(test.currentVersion)
   316  		s.PatchValue(&jujuversion.Current, current.Number)
   317  		s.PatchValue(&arch.HostArch, func() string { return current.Arch })
   318  		s.PatchValue(&series.HostSeries, func() string { return current.Series })
   319  		com := newUpgradeJujuCommand(test.upgradeMap)
   320  		if err := coretesting.InitCommand(com, test.args); err != nil {
   321  			if test.expectInitErr != "" {
   322  				c.Check(err, gc.ErrorMatches, test.expectInitErr)
   323  			} else {
   324  				c.Check(err, jc.ErrorIsNil)
   325  			}
   326  			continue
   327  		}
   328  
   329  		// Set up state and environ, and run the command.
   330  		toolsDir := c.MkDir()
   331  		updateAttrs := map[string]interface{}{
   332  			"agent-version":      test.agentVersion,
   333  			"agent-metadata-url": "file://" + toolsDir + "/tools",
   334  		}
   335  		err := s.State.UpdateModelConfig(updateAttrs, nil, nil)
   336  		c.Assert(err, jc.ErrorIsNil)
   337  		versions := make([]version.Binary, len(test.tools))
   338  		for i, v := range test.tools {
   339  			versions[i] = version.MustParseBinary(v)
   340  		}
   341  		if len(versions) > 0 {
   342  			stor, err := filestorage.NewFileStorageWriter(toolsDir)
   343  			c.Assert(err, jc.ErrorIsNil)
   344  			envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...)
   345  		}
   346  
   347  		err = com.Run(coretesting.Context(c))
   348  		if test.expectErr != "" {
   349  			c.Check(err, gc.ErrorMatches, test.expectErr)
   350  			continue
   351  		} else if !c.Check(err, jc.ErrorIsNil) {
   352  			continue
   353  		}
   354  
   355  		// Check expected changes to environ/state.
   356  		cfg, err := s.State.ModelConfig()
   357  		c.Check(err, jc.ErrorIsNil)
   358  		agentVersion, ok := cfg.AgentVersion()
   359  		c.Check(ok, jc.IsTrue)
   360  		c.Check(agentVersion, gc.Equals, version.MustParse(test.expectVersion))
   361  
   362  		for _, uploaded := range test.expectUploaded {
   363  			// Substitute latest LTS for placeholder in expected series for uploaded tools
   364  			uploaded = strings.Replace(uploaded, "%LTS%", config.LatestLtsSeries(), 1)
   365  			vers := version.MustParseBinary(uploaded)
   366  			s.checkToolsUploaded(c, vers, agentVersion)
   367  		}
   368  	}
   369  }
   370  
   371  func (s *UpgradeJujuSuite) checkToolsUploaded(c *gc.C, vers version.Binary, agentVersion version.Number) {
   372  	storage, err := s.State.ToolsStorage()
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	defer storage.Close()
   375  	_, r, err := storage.Open(vers.String())
   376  	if !c.Check(err, jc.ErrorIsNil) {
   377  		return
   378  	}
   379  	data, err := ioutil.ReadAll(r)
   380  	r.Close()
   381  	c.Check(err, jc.ErrorIsNil)
   382  	expectContent := version.Binary{
   383  		Number: agentVersion,
   384  		Arch:   arch.HostArch(),
   385  		Series: series.HostSeries(),
   386  	}
   387  	checkToolsContent(c, data, "jujud contents "+expectContent.String())
   388  }
   389  
   390  func checkToolsContent(c *gc.C, data []byte, uploaded string) {
   391  	zr, err := gzip.NewReader(bytes.NewReader(data))
   392  	c.Check(err, jc.ErrorIsNil)
   393  	defer zr.Close()
   394  	tr := tar.NewReader(zr)
   395  	found := false
   396  	for {
   397  		hdr, err := tr.Next()
   398  		if err == io.EOF {
   399  			break
   400  		}
   401  		c.Check(err, jc.ErrorIsNil)
   402  		if strings.ContainsAny(hdr.Name, "/\\") {
   403  			c.Fail()
   404  		}
   405  		if hdr.Typeflag != tar.TypeReg {
   406  			c.Fail()
   407  		}
   408  		content, err := ioutil.ReadAll(tr)
   409  		c.Check(err, jc.ErrorIsNil)
   410  		c.Check(string(content), gc.Equals, uploaded)
   411  		found = true
   412  	}
   413  	c.Check(found, jc.IsTrue)
   414  }
   415  
   416  // JujuConnSuite very helpfully uploads some default
   417  // tools to the environment's storage. We don't want
   418  // 'em there; but we do want a consistent default-series
   419  // in the environment state.
   420  func (s *UpgradeJujuSuite) Reset(c *gc.C) {
   421  	s.JujuConnSuite.Reset(c)
   422  	envtesting.RemoveTools(c, s.DefaultToolsStorage, s.Environ.Config().AgentStream())
   423  	updateAttrs := map[string]interface{}{
   424  		"default-series": "raring",
   425  		"agent-version":  "1.2.3",
   426  	}
   427  	err := s.State.UpdateModelConfig(updateAttrs, nil, nil)
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	s.PatchValue(&sync.BuildToolsTarball, toolstesting.GetMockBuildTools(c))
   430  
   431  	// Set API host ports so FindTools works.
   432  	hostPorts := [][]network.HostPort{
   433  		network.NewHostPorts(1234, "0.1.2.3"),
   434  	}
   435  	err = s.State.SetAPIHostPorts(hostPorts)
   436  	c.Assert(err, jc.ErrorIsNil)
   437  
   438  	s.CmdBlockHelper = cmdcommon.NewCmdBlockHelper(s.APIState)
   439  	c.Assert(s.CmdBlockHelper, gc.NotNil)
   440  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
   441  }
   442  
   443  func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealUpload(c *gc.C) {
   444  	s.Reset(c)
   445  	s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99"))
   446  	cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")})
   447  	_, err := coretesting.RunCommand(c, cmd, "--upload-tools")
   448  	c.Assert(err, jc.ErrorIsNil)
   449  	vers := version.Binary{
   450  		Number: jujuversion.Current,
   451  		Arch:   arch.HostArch(),
   452  		Series: series.HostSeries(),
   453  	}
   454  	vers.Build = 1
   455  	s.checkToolsUploaded(c, vers, vers.Number)
   456  }
   457  
   458  func (s *UpgradeJujuSuite) TestBlockUpgradeJujuWithRealUpload(c *gc.C) {
   459  	s.Reset(c)
   460  	s.PatchValue(&jujuversion.Current, version.MustParse("1.99.99"))
   461  	cmd := newUpgradeJujuCommand(map[int]version.Number{2: version.MustParse("1.99.99")})
   462  	// Block operation
   463  	s.BlockAllChanges(c, "TestBlockUpgradeJujuWithRealUpload")
   464  	_, err := coretesting.RunCommand(c, cmd, "--upload-tools")
   465  	s.AssertBlocked(c, err, ".*TestBlockUpgradeJujuWithRealUpload.*")
   466  }
   467  
   468  type DryRunTest struct {
   469  	about             string
   470  	cmdArgs           []string
   471  	tools             []string
   472  	currentVersion    string
   473  	agentVersion      string
   474  	expectedCmdOutput string
   475  }
   476  
   477  func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) {
   478  	tests := []DryRunTest{
   479  		{
   480  			about:          "dry run outputs and doesn't change anything when uploading tools",
   481  			cmdArgs:        []string{"--upload-tools", "--dry-run"},
   482  			tools:          []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64", "2.2.3-quantal-amd64"},
   483  			currentVersion: "2.1.3-quantal-amd64",
   484  			agentVersion:   "2.0.0",
   485  			expectedCmdOutput: `available tools:
   486      2.1.3-quantal-amd64
   487  best version:
   488      2.1.3
   489  upgrade to this version by running
   490      juju upgrade-juju --version="2.1.3"
   491  `,
   492  		},
   493  		{
   494  			about:          "dry run outputs and doesn't change anything",
   495  			cmdArgs:        []string{"--dry-run"},
   496  			tools:          []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64", "2.2.3-quantal-amd64"},
   497  			currentVersion: "2.0.0-quantal-amd64",
   498  			agentVersion:   "2.0.0",
   499  			expectedCmdOutput: `available tools:
   500      2.1-dev1-quantal-amd64
   501      2.1.0-quantal-amd64
   502      2.1.2-quantal-i386
   503      2.1.3-quantal-amd64
   504      2.2.3-quantal-amd64
   505  best version:
   506      2.1.3
   507  upgrade to this version by running
   508      juju upgrade-juju --version="2.1.3"
   509  `,
   510  		},
   511  		{
   512  			about:          "dry run ignores unknown series",
   513  			cmdArgs:        []string{"--dry-run"},
   514  			tools:          []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"},
   515  			currentVersion: "2.0.0-quantal-amd64",
   516  			agentVersion:   "2.0.0",
   517  			expectedCmdOutput: `available tools:
   518      2.1.0-quantal-amd64
   519      2.1.2-quantal-i386
   520      2.1.3-quantal-amd64
   521  best version:
   522      2.1.3
   523  upgrade to this version by running
   524      juju upgrade-juju --version="2.1.3"
   525  `,
   526  		},
   527  	}
   528  
   529  	for i, test := range tests {
   530  		c.Logf("\ntest %d: %s", i, test.about)
   531  		s.Reset(c)
   532  		tools.DefaultBaseURL = ""
   533  
   534  		s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools)
   535  
   536  		com := newUpgradeJujuCommand(nil)
   537  		err := coretesting.InitCommand(com, test.cmdArgs)
   538  		c.Assert(err, jc.ErrorIsNil)
   539  
   540  		ctx := coretesting.Context(c)
   541  		err = com.Run(ctx)
   542  		c.Assert(err, jc.ErrorIsNil)
   543  
   544  		// Check agent version doesn't change
   545  		cfg, err := s.State.ModelConfig()
   546  		c.Assert(err, jc.ErrorIsNil)
   547  		agentVer, ok := cfg.AgentVersion()
   548  		c.Assert(ok, jc.IsTrue)
   549  		c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion))
   550  		output := coretesting.Stderr(ctx)
   551  		c.Assert(output, gc.Equals, test.expectedCmdOutput)
   552  	}
   553  }
   554  
   555  func (s *UpgradeJujuSuite) setUpEnvAndTools(c *gc.C, currentVersion string, agentVersion string, tools []string) {
   556  	current := version.MustParseBinary(currentVersion)
   557  	s.PatchValue(&jujuversion.Current, current.Number)
   558  	s.PatchValue(&arch.HostArch, func() string { return current.Arch })
   559  	s.PatchValue(&series.HostSeries, func() string { return current.Series })
   560  
   561  	toolsDir := c.MkDir()
   562  	updateAttrs := map[string]interface{}{
   563  		"agent-version":      agentVersion,
   564  		"agent-metadata-url": "file://" + toolsDir + "/tools",
   565  	}
   566  
   567  	err := s.State.UpdateModelConfig(updateAttrs, nil, nil)
   568  	c.Assert(err, jc.ErrorIsNil)
   569  	versions := make([]version.Binary, len(tools))
   570  	for i, v := range tools {
   571  		versions[i], err = version.ParseBinary(v)
   572  		if err != nil {
   573  			c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError)
   574  		}
   575  	}
   576  	if len(versions) > 0 {
   577  		stor, err := filestorage.NewFileStorageWriter(toolsDir)
   578  		c.Assert(err, jc.ErrorIsNil)
   579  		envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...)
   580  	}
   581  }
   582  
   583  func (s *UpgradeJujuSuite) TestUpgradesDifferentMajor(c *gc.C) {
   584  	toolsList49Only := `available tools:
   585      4.9.0-trusty-amd64
   586  best version:
   587      4.9.0
   588  `
   589  	tests := []struct {
   590  		about             string
   591  		cmdArgs           []string
   592  		tools             []string
   593  		currentVersion    string
   594  		agentVersion      string
   595  		expectedVersion   string
   596  		expectedCmdOutput string
   597  		expectedLogOutput string
   598  		excludedLogOutput string
   599  		expectedErr       string
   600  		upgradeMap        map[int]version.Number
   601  	}{{
   602  		about:             "upgrade previous major to latest previous major",
   603  		tools:             []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"},
   604  		currentVersion:    "5.0.0-trusty-amd64",
   605  		agentVersion:      "4.8.5",
   606  		expectedVersion:   "4.9.0",
   607  		expectedCmdOutput: toolsList49Only,
   608  		expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.0\).*started upgrade to 4.9.0.*`,
   609  	}, {
   610  		about:             "upgrade previous major to latest previous major --dry-run still warns",
   611  		tools:             []string{"5.0.1-trusty-amd64", "4.9.0-trusty-amd64"},
   612  		currentVersion:    "5.0.1-trusty-amd64",
   613  		agentVersion:      "4.8.5",
   614  		expectedVersion:   "4.9.0",
   615  		expectedCmdOutput: toolsList49Only,
   616  		expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.1\).*started upgrade to 4.9.0.*`,
   617  	}, {
   618  		about:             "upgrade previous major to latest previous major with --version",
   619  		cmdArgs:           []string{"--version=4.9.0"},
   620  		tools:             []string{"5.0.2-trusty-amd64", "4.9.0-trusty-amd64", "4.8.0-trusty-amd64"},
   621  		currentVersion:    "5.0.2-trusty-amd64",
   622  		agentVersion:      "4.7.5",
   623  		expectedVersion:   "4.9.0",
   624  		expectedCmdOutput: toolsList49Only,
   625  		expectedLogOutput: `.*version 4.9.0 incompatible with this client \(5.0.2\).*started upgrade to 4.9.0.*`,
   626  	}, {
   627  		about:             "can upgrade lower major version to current major version at minimum level",
   628  		cmdArgs:           []string{"--version=6.0.5"},
   629  		tools:             []string{"6.0.5-trusty-amd64", "5.9.9-trusty-amd64"},
   630  		currentVersion:    "6.0.0-trusty-amd64",
   631  		agentVersion:      "5.9.8",
   632  		expectedVersion:   "6.0.5",
   633  		excludedLogOutput: `incompatible with this client (6.0.0)`,
   634  		upgradeMap:        map[int]version.Number{6: version.MustParse("5.9.8")},
   635  	}, {
   636  		about:             "can upgrade lower major version to current major version above minimum level",
   637  		cmdArgs:           []string{"--version=6.0.5"},
   638  		tools:             []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"},
   639  		currentVersion:    "6.0.1-trusty-amd64",
   640  		agentVersion:      "5.10.8",
   641  		expectedVersion:   "6.0.5",
   642  		excludedLogOutput: `incompatible with this client (6.0.1)`,
   643  		upgradeMap:        map[int]version.Number{6: version.MustParse("5.9.8")},
   644  	}, {
   645  		about:           "can upgrade current to next major version",
   646  		cmdArgs:         []string{"--version=6.0.5"},
   647  		tools:           []string{"6.0.5-trusty-amd64", "5.11.0-trusty-amd64"},
   648  		currentVersion:  "5.10.8-trusty-amd64",
   649  		agentVersion:    "5.10.8",
   650  		expectedVersion: "6.0.5",
   651  		upgradeMap:      map[int]version.Number{6: version.MustParse("5.9.8")},
   652  	}, {
   653  		about:             "upgrade fails if not at minimum version",
   654  		cmdArgs:           []string{"--version=7.0.1"},
   655  		tools:             []string{"7.0.1-trusty-amd64"},
   656  		currentVersion:    "7.0.1-trusty-amd64",
   657  		agentVersion:      "6.0.0",
   658  		expectedVersion:   "6.0.0",
   659  		expectedCmdOutput: "upgrades to a new major version must first go through 6.7.8\n",
   660  		expectedErr:       "unable to upgrade to requested version",
   661  		upgradeMap:        map[int]version.Number{7: version.MustParse("6.7.8")},
   662  	}, {
   663  		about:             "upgrade fails if not a minor of 0",
   664  		cmdArgs:           []string{"--version=7.1.1"},
   665  		tools:             []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"},
   666  		currentVersion:    "7.0.1-trusty-amd64",
   667  		agentVersion:      "6.7.8",
   668  		expectedVersion:   "6.7.8",
   669  		expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n",
   670  		expectedErr:       "unable to upgrade to requested version",
   671  		upgradeMap:        map[int]version.Number{7: version.MustParse("6.7.8")},
   672  	}, {
   673  		about:           "upgrade fails if not at minimum version and not a minor of 0",
   674  		cmdArgs:         []string{"--version=7.1.1"},
   675  		tools:           []string{"7.0.1-trusty-amd64", "7.1.1-trusty-amd64"},
   676  		currentVersion:  "7.0.1-trusty-amd64",
   677  		agentVersion:    "6.0.0",
   678  		expectedVersion: "6.0.0",
   679  		expectedCmdOutput: "upgrades to 7.1.1 must first go through juju 7.0\n" +
   680  			"upgrades to a new major version must first go through 6.7.8\n",
   681  		expectedErr: "unable to upgrade to requested version",
   682  		upgradeMap:  map[int]version.Number{7: version.MustParse("6.7.8")},
   683  	}}
   684  	for i, test := range tests {
   685  		c.Logf("\ntest %d: %s", i, test.about)
   686  		s.Reset(c)
   687  		tools.DefaultBaseURL = ""
   688  
   689  		s.setUpEnvAndTools(c, test.currentVersion, test.agentVersion, test.tools)
   690  
   691  		com := newUpgradeJujuCommand(test.upgradeMap)
   692  		err := coretesting.InitCommand(com, test.cmdArgs)
   693  		c.Assert(err, jc.ErrorIsNil)
   694  
   695  		ctx := coretesting.Context(c)
   696  		err = com.Run(ctx)
   697  		if test.expectedErr != "" {
   698  			c.Check(err, gc.ErrorMatches, test.expectedErr)
   699  		} else if !c.Check(err, jc.ErrorIsNil) {
   700  			continue
   701  		}
   702  
   703  		// Check agent version doesn't change
   704  		cfg, err := s.State.ModelConfig()
   705  		c.Assert(err, jc.ErrorIsNil)
   706  		agentVer, ok := cfg.AgentVersion()
   707  		c.Assert(ok, jc.IsTrue)
   708  		c.Check(agentVer, gc.Equals, version.MustParse(test.expectedVersion))
   709  		output := coretesting.Stderr(ctx)
   710  		if test.expectedCmdOutput != "" {
   711  			c.Check(output, gc.Equals, test.expectedCmdOutput)
   712  		}
   713  		if test.expectedLogOutput != "" {
   714  			c.Check(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, test.expectedLogOutput)
   715  		}
   716  		if test.excludedLogOutput != "" {
   717  			c.Check(c.GetTestLog(), gc.Not(jc.Contains), test.excludedLogOutput)
   718  		}
   719  	}
   720  }
   721  
   722  func (s *UpgradeJujuSuite) TestUpgradeUnknownSeriesInStreams(c *gc.C) {
   723  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   724  	fakeAPI.addTools("2.1.0-weird-amd64")
   725  	fakeAPI.patch(s)
   726  
   727  	cmd := &upgradeJujuCommand{}
   728  	err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{})
   729  	c.Assert(err, jc.ErrorIsNil)
   730  
   731  	err = modelcmd.Wrap(cmd).Run(coretesting.Context(c))
   732  	c.Assert(err, gc.IsNil)
   733  
   734  	// ensure find tools was called
   735  	c.Assert(fakeAPI.findToolsCalled, jc.IsTrue)
   736  	c.Assert(fakeAPI.tools, gc.DeepEquals, []string{"2.1.0-weird-amd64", fakeAPI.nextVersion.String()})
   737  }
   738  
   739  func (s *UpgradeJujuSuite) TestUpgradeInProgress(c *gc.C) {
   740  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   741  	fakeAPI.setVersionErr = &params.Error{
   742  		Message: "a message from the server about the problem",
   743  		Code:    params.CodeUpgradeInProgress,
   744  	}
   745  	fakeAPI.patch(s)
   746  	cmd := &upgradeJujuCommand{}
   747  	err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{})
   748  	c.Assert(err, jc.ErrorIsNil)
   749  
   750  	err = modelcmd.Wrap(cmd).Run(coretesting.Context(c))
   751  	c.Assert(err, gc.ErrorMatches, "a message from the server about the problem\n"+
   752  		"\n"+
   753  		"Please wait for the upgrade to complete or if there was a problem with\n"+
   754  		"the last upgrade that has been resolved, consider running the\n"+
   755  		"upgrade-juju command with the --reset-previous-upgrade flag.",
   756  	)
   757  }
   758  
   759  func (s *UpgradeJujuSuite) TestBlockUpgradeInProgress(c *gc.C) {
   760  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   761  	fakeAPI.setVersionErr = common.OperationBlockedError("the operation has been blocked")
   762  	fakeAPI.patch(s)
   763  	cmd := &upgradeJujuCommand{}
   764  	err := coretesting.InitCommand(modelcmd.Wrap(cmd), []string{})
   765  	c.Assert(err, jc.ErrorIsNil)
   766  
   767  	// Block operation
   768  	s.BlockAllChanges(c, "TestBlockUpgradeInProgress")
   769  	err = modelcmd.Wrap(cmd).Run(coretesting.Context(c))
   770  	s.AssertBlocked(c, err, ".*To unblock changes.*")
   771  }
   772  
   773  func (s *UpgradeJujuSuite) TestResetPreviousUpgrade(c *gc.C) {
   774  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   775  	fakeAPI.patch(s)
   776  
   777  	ctx := coretesting.Context(c)
   778  	var stdin bytes.Buffer
   779  	ctx.Stdin = &stdin
   780  
   781  	run := func(answer string, expect bool, args ...string) {
   782  		stdin.Reset()
   783  		if answer != "" {
   784  			stdin.WriteString(answer)
   785  		}
   786  
   787  		fakeAPI.reset()
   788  
   789  		cmd := &upgradeJujuCommand{}
   790  		err := coretesting.InitCommand(modelcmd.Wrap(cmd),
   791  			append([]string{"--reset-previous-upgrade"}, args...))
   792  		c.Assert(err, jc.ErrorIsNil)
   793  		err = modelcmd.Wrap(cmd).Run(ctx)
   794  		if expect {
   795  			c.Assert(err, jc.ErrorIsNil)
   796  		} else {
   797  			c.Assert(err, gc.ErrorMatches, "previous upgrade not reset and no new upgrade triggered")
   798  		}
   799  
   800  		c.Assert(fakeAPI.abortCurrentUpgradeCalled, gc.Equals, expect)
   801  		expectedVersion := version.Number{}
   802  		if expect {
   803  			expectedVersion = fakeAPI.nextVersion.Number
   804  		}
   805  		c.Assert(fakeAPI.setVersionCalledWith, gc.Equals, expectedVersion)
   806  	}
   807  
   808  	const expectUpgrade = true
   809  	const expectNoUpgrade = false
   810  
   811  	// EOF on stdin - equivalent to answering no.
   812  	run("", expectNoUpgrade)
   813  
   814  	// -y on command line - no confirmation required
   815  	run("", expectUpgrade, "-y")
   816  
   817  	// --yes on command line - no confirmation required
   818  	run("", expectUpgrade, "--yes")
   819  
   820  	// various ways of saying "yes" to the prompt
   821  	for _, answer := range []string{"y", "Y", "yes", "YES"} {
   822  		run(answer, expectUpgrade)
   823  	}
   824  
   825  	// various ways of saying "no" to the prompt
   826  	for _, answer := range []string{"n", "N", "no", "foo"} {
   827  		run(answer, expectNoUpgrade)
   828  	}
   829  }
   830  
   831  func NewFakeUpgradeJujuAPI(c *gc.C, st *state.State) *fakeUpgradeJujuAPI {
   832  	nextVersion := version.Binary{
   833  		Number: jujuversion.Current,
   834  		Arch:   arch.HostArch(),
   835  		Series: series.HostSeries(),
   836  	}
   837  	nextVersion.Minor++
   838  	return &fakeUpgradeJujuAPI{
   839  		c:           c,
   840  		st:          st,
   841  		nextVersion: nextVersion,
   842  	}
   843  }
   844  
   845  type fakeUpgradeJujuAPI struct {
   846  	c                         *gc.C
   847  	st                        *state.State
   848  	nextVersion               version.Binary
   849  	setVersionErr             error
   850  	abortCurrentUpgradeCalled bool
   851  	setVersionCalledWith      version.Number
   852  	tools                     []string
   853  	findToolsCalled           bool
   854  }
   855  
   856  func (a *fakeUpgradeJujuAPI) reset() {
   857  	a.setVersionErr = nil
   858  	a.abortCurrentUpgradeCalled = false
   859  	a.setVersionCalledWith = version.Number{}
   860  	a.tools = []string{}
   861  	a.findToolsCalled = false
   862  }
   863  
   864  func (a *fakeUpgradeJujuAPI) patch(s *UpgradeJujuSuite) {
   865  	s.PatchValue(&getUpgradeJujuAPI, func(*upgradeJujuCommand) (upgradeJujuAPI, error) {
   866  		return a, nil
   867  	})
   868  }
   869  
   870  func (a *fakeUpgradeJujuAPI) addTools(tools ...string) {
   871  	for _, tool := range tools {
   872  		a.tools = append(a.tools, tool)
   873  	}
   874  }
   875  
   876  func (a *fakeUpgradeJujuAPI) ModelGet() (map[string]interface{}, error) {
   877  	config, err := a.st.ModelConfig()
   878  	if err != nil {
   879  		return make(map[string]interface{}), err
   880  	}
   881  	return config.AllAttrs(), nil
   882  }
   883  
   884  func (a *fakeUpgradeJujuAPI) FindTools(majorVersion, minorVersion int, series, arch string) (
   885  	result params.FindToolsResult, err error,
   886  ) {
   887  	a.findToolsCalled = true
   888  	a.tools = append(a.tools, a.nextVersion.String())
   889  	tools := toolstesting.MakeTools(a.c, a.c.MkDir(), "released", a.tools)
   890  	return params.FindToolsResult{
   891  		List:  tools,
   892  		Error: nil,
   893  	}, nil
   894  }
   895  
   896  func (a *fakeUpgradeJujuAPI) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error) {
   897  	panic("not implemented")
   898  }
   899  
   900  func (a *fakeUpgradeJujuAPI) AbortCurrentUpgrade() error {
   901  	a.abortCurrentUpgradeCalled = true
   902  	return nil
   903  }
   904  
   905  func (a *fakeUpgradeJujuAPI) SetModelAgentVersion(v version.Number) error {
   906  	a.setVersionCalledWith = v
   907  	return a.setVersionErr
   908  }
   909  
   910  func (a *fakeUpgradeJujuAPI) Close() error {
   911  	return nil
   912  }