github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/params"
    19  	apiservertesting "github.com/juju/juju/apiserver/testing"
    20  	"github.com/juju/juju/cmd/envcmd"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/filestorage"
    23  	"github.com/juju/juju/environs/sync"
    24  	envtesting "github.com/juju/juju/environs/testing"
    25  	"github.com/juju/juju/environs/tools"
    26  	toolstesting "github.com/juju/juju/environs/tools/testing"
    27  	jujutesting "github.com/juju/juju/juju/testing"
    28  	"github.com/juju/juju/network"
    29  	_ "github.com/juju/juju/provider/dummy"
    30  	"github.com/juju/juju/state"
    31  	coretesting "github.com/juju/juju/testing"
    32  	coretools "github.com/juju/juju/tools"
    33  	"github.com/juju/juju/version"
    34  )
    35  
    36  type UpgradeJujuSuite struct {
    37  	jujutesting.JujuConnSuite
    38  
    39  	resources  *common.Resources
    40  	authoriser apiservertesting.FakeAuthorizer
    41  
    42  	toolsDir string
    43  	CmdBlockHelper
    44  }
    45  
    46  func (s *UpgradeJujuSuite) SetUpTest(c *gc.C) {
    47  	s.JujuConnSuite.SetUpTest(c)
    48  	s.resources = common.NewResources()
    49  	s.authoriser = apiservertesting.FakeAuthorizer{
    50  		Tag: s.AdminUserTag(c),
    51  	}
    52  
    53  	s.CmdBlockHelper = NewCmdBlockHelper(s.APIState)
    54  	c.Assert(s.CmdBlockHelper, gc.NotNil)
    55  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
    56  }
    57  
    58  var _ = gc.Suite(&UpgradeJujuSuite{})
    59  
    60  var upgradeJujuTests = []struct {
    61  	about          string
    62  	tools          []string
    63  	currentVersion string
    64  	agentVersion   string
    65  
    66  	args           []string
    67  	expectInitErr  string
    68  	expectErr      string
    69  	expectVersion  string
    70  	expectUploaded []string
    71  }{{
    72  	about:          "unwanted extra argument",
    73  	currentVersion: "1.0.0-quantal-amd64",
    74  	args:           []string{"foo"},
    75  	expectInitErr:  "unrecognized args:.*",
    76  }, {
    77  	about:          "removed arg --dev specified",
    78  	currentVersion: "1.0.0-quantal-amd64",
    79  	args:           []string{"--dev"},
    80  	expectInitErr:  "flag provided but not defined: --dev",
    81  }, {
    82  	about:          "invalid --version value",
    83  	currentVersion: "1.0.0-quantal-amd64",
    84  	args:           []string{"--version", "invalid-version"},
    85  	expectInitErr:  "invalid version .*",
    86  }, {
    87  	about:          "just major version, no minor specified",
    88  	currentVersion: "4.2.0-quantal-amd64",
    89  	args:           []string{"--version", "4"},
    90  	expectInitErr:  `invalid version "4"`,
    91  }, {
    92  	about:          "major version upgrade to incompatible version",
    93  	currentVersion: "2.0.0-quantal-amd64",
    94  	args:           []string{"--version", "5.2.0"},
    95  	expectInitErr:  "cannot upgrade to version incompatible with CLI",
    96  }, {
    97  	about:          "major version downgrade to incompatible version",
    98  	currentVersion: "4.2.0-quantal-amd64",
    99  	args:           []string{"--version", "3.2.0"},
   100  	expectInitErr:  "cannot upgrade to version incompatible with CLI",
   101  }, {
   102  	about:          "invalid --series",
   103  	currentVersion: "4.2.0-quantal-amd64",
   104  	args:           []string{"--series", "precise&quantal"},
   105  	expectInitErr:  `invalid value "precise&quantal" for flag --series: .*`,
   106  }, {
   107  	about:          "--series without --upload-tools",
   108  	currentVersion: "4.2.0-quantal-amd64",
   109  	args:           []string{"--series", "precise,quantal"},
   110  	expectInitErr:  "--series requires --upload-tools",
   111  }, {
   112  	about:          "--upload-tools with inappropriate version 1",
   113  	currentVersion: "4.2.0-quantal-amd64",
   114  	args:           []string{"--upload-tools", "--version", "3.1.0"},
   115  	expectInitErr:  "cannot upgrade to version incompatible with CLI",
   116  }, {
   117  	about:          "--upload-tools with inappropriate version 2",
   118  	currentVersion: "3.2.7-quantal-amd64",
   119  	args:           []string{"--upload-tools", "--version", "3.2.8.4"},
   120  	expectInitErr:  "cannot specify build number when uploading tools",
   121  }, {
   122  	about:          "latest supported stable release",
   123  	tools:          []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "2.1-dev1-quantal-amd64"},
   124  	currentVersion: "2.0.0-quantal-amd64",
   125  	agentVersion:   "2.0.0",
   126  	expectVersion:  "2.1.3",
   127  }, {
   128  	about:          "latest current release",
   129  	tools:          []string{"2.0.5-quantal-amd64", "2.0.1-quantal-i386", "2.3.3-quantal-amd64"},
   130  	currentVersion: "2.0.0-quantal-amd64",
   131  	agentVersion:   "2.0.0",
   132  	expectVersion:  "2.0.5",
   133  }, {
   134  	about:          "latest current release matching CLI, major version",
   135  	tools:          []string{"3.2.0-quantal-amd64"},
   136  	currentVersion: "3.2.0-quantal-amd64",
   137  	agentVersion:   "2.8.2",
   138  	expectVersion:  "3.2.0",
   139  }, {
   140  	about:          "latest current release matching CLI, major version, no matching major tools",
   141  	tools:          []string{"2.8.2-quantal-amd64"},
   142  	currentVersion: "3.2.0-quantal-amd64",
   143  	agentVersion:   "2.8.2",
   144  	expectErr:      "no matching tools available",
   145  }, {
   146  	about:          "latest current release matching CLI, major version, no matching tools",
   147  	tools:          []string{"3.3.0-quantal-amd64"},
   148  	currentVersion: "3.2.0-quantal-amd64",
   149  	agentVersion:   "2.8.2",
   150  	expectErr:      "no compatible tools available",
   151  }, {
   152  	about:          "no next supported available",
   153  	tools:          []string{"2.2.0-quantal-amd64", "2.2.5-quantal-i386", "2.3.3-quantal-amd64", "2.1-dev1-quantal-amd64"},
   154  	currentVersion: "2.0.0-quantal-amd64",
   155  	agentVersion:   "2.0.0",
   156  	expectErr:      "no more recent supported versions available",
   157  }, {
   158  	about:          "latest supported stable, when client is dev",
   159  	tools:          []string{"2.1-dev1-quantal-amd64", "2.1.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"},
   160  	currentVersion: "2.1-dev0-quantal-amd64",
   161  	agentVersion:   "2.0.0",
   162  	expectVersion:  "2.1.0",
   163  }, {
   164  	about:          "latest current, when agent is dev",
   165  	tools:          []string{"2.1-dev1-quantal-amd64", "2.2.0-quantal-amd64", "2.3-dev0-quantal-amd64", "3.0.1-quantal-amd64"},
   166  	currentVersion: "2.0.0-quantal-amd64",
   167  	agentVersion:   "2.1-dev0",
   168  	expectVersion:  "2.2.0",
   169  }, {
   170  	about:          "specified version",
   171  	tools:          []string{"2.3-dev0-quantal-amd64"},
   172  	currentVersion: "2.0.0-quantal-amd64",
   173  	agentVersion:   "2.0.0",
   174  	args:           []string{"--version", "2.3-dev0"},
   175  	expectVersion:  "2.3-dev0",
   176  }, {
   177  	about:          "specified major version",
   178  	tools:          []string{"3.2.0-quantal-amd64"},
   179  	currentVersion: "3.2.0-quantal-amd64",
   180  	agentVersion:   "2.8.2",
   181  	args:           []string{"--version", "3.2.0"},
   182  	expectVersion:  "3.2.0",
   183  }, {
   184  	about:          "specified version missing, but already set",
   185  	currentVersion: "3.0.0-quantal-amd64",
   186  	agentVersion:   "3.0.0",
   187  	args:           []string{"--version", "3.0.0"},
   188  	expectVersion:  "3.0.0",
   189  }, {
   190  	about:          "specified version, no tools",
   191  	currentVersion: "3.0.0-quantal-amd64",
   192  	agentVersion:   "3.0.0",
   193  	args:           []string{"--version", "3.2.0"},
   194  	expectErr:      "no tools available",
   195  }, {
   196  	about:          "specified version, no matching major version",
   197  	tools:          []string{"4.2.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 minor version",
   204  	tools:          []string{"3.4.0-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 patch version",
   211  	tools:          []string{"3.2.5-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:          "specified version, no matching build version",
   218  	tools:          []string{"3.2.0.2-quantal-amd64"},
   219  	currentVersion: "3.0.0-quantal-amd64",
   220  	agentVersion:   "3.0.0",
   221  	args:           []string{"--version", "3.2.0"},
   222  	expectErr:      "no matching tools available",
   223  }, {
   224  	about:          "major version downgrade to incompatible version",
   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 change version from 4.2.0 to 3.2.0",
   230  }, {
   231  	about:          "minor version downgrade to incompatible version",
   232  	tools:          []string{"3.2.0-quantal-amd64"},
   233  	currentVersion: "3.2.0-quantal-amd64",
   234  	agentVersion:   "3.3-dev0",
   235  	args:           []string{"--version", "3.2.0"},
   236  	expectErr:      "cannot change version from 3.3-dev0 to 3.2.0",
   237  }, {
   238  	about:          "nothing available",
   239  	currentVersion: "2.0.0-quantal-amd64",
   240  	agentVersion:   "2.0.0",
   241  	expectVersion:  "2.0.0",
   242  }, {
   243  	about:          "nothing available 2",
   244  	currentVersion: "2.0.0-quantal-amd64",
   245  	tools:          []string{"3.2.0-quantal-amd64"},
   246  	agentVersion:   "2.0.0",
   247  	expectVersion:  "2.0.0",
   248  }, {
   249  	about:          "upload with default series",
   250  	currentVersion: "2.2.0-quantal-amd64",
   251  	agentVersion:   "2.0.0",
   252  	args:           []string{"--upload-tools"},
   253  	expectVersion:  "2.2.0.1",
   254  	expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-%LTS%-amd64", "2.2.0.1-raring-amd64"},
   255  }, {
   256  	about:          "upload with explicit version",
   257  	currentVersion: "2.2.0-quantal-amd64",
   258  	agentVersion:   "2.0.0",
   259  	args:           []string{"--upload-tools", "--version", "2.7.3"},
   260  	expectVersion:  "2.7.3.1",
   261  	expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-%LTS%-amd64", "2.7.3.1-raring-amd64"},
   262  }, {
   263  	about:          "upload with explicit series",
   264  	currentVersion: "2.2.0-quantal-amd64",
   265  	agentVersion:   "2.0.0",
   266  	args:           []string{"--upload-tools", "--series", "raring"},
   267  	expectVersion:  "2.2.0.1",
   268  	expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.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  	oldVersion := version.Current
   310  	defer func() {
   311  		version.Current = oldVersion
   312  	}()
   313  
   314  	for i, test := range upgradeJujuTests {
   315  		c.Logf("\ntest %d: %s", i, test.about)
   316  		s.Reset(c)
   317  		tools.DefaultBaseURL = ""
   318  
   319  		// Set up apparent CLI version and initialize the command.
   320  		version.Current = version.MustParseBinary(test.currentVersion)
   321  		com := &UpgradeJujuCommand{}
   322  		if err := coretesting.InitCommand(envcmd.Wrap(com), test.args); err != nil {
   323  			if test.expectInitErr != "" {
   324  				c.Check(err, gc.ErrorMatches, test.expectInitErr)
   325  			} else {
   326  				c.Check(err, jc.ErrorIsNil)
   327  			}
   328  			continue
   329  		}
   330  
   331  		// Set up state and environ, and run the command.
   332  		toolsDir := c.MkDir()
   333  		updateAttrs := map[string]interface{}{
   334  			"agent-version":      test.agentVersion,
   335  			"agent-metadata-url": "file://" + toolsDir + "/tools",
   336  		}
   337  		err := s.State.UpdateEnvironConfig(updateAttrs, nil, nil)
   338  		c.Assert(err, jc.ErrorIsNil)
   339  		versions := make([]version.Binary, len(test.tools))
   340  		for i, v := range test.tools {
   341  			versions[i] = version.MustParseBinary(v)
   342  		}
   343  		if len(versions) > 0 {
   344  			stor, err := filestorage.NewFileStorageWriter(toolsDir)
   345  			c.Assert(err, jc.ErrorIsNil)
   346  			envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...)
   347  		}
   348  
   349  		err = com.Run(coretesting.Context(c))
   350  		if test.expectErr != "" {
   351  			c.Check(err, gc.ErrorMatches, test.expectErr)
   352  			continue
   353  		} else if !c.Check(err, jc.ErrorIsNil) {
   354  			continue
   355  		}
   356  
   357  		// Check expected changes to environ/state.
   358  		cfg, err := s.State.EnvironConfig()
   359  		c.Check(err, jc.ErrorIsNil)
   360  		agentVersion, ok := cfg.AgentVersion()
   361  		c.Check(ok, jc.IsTrue)
   362  		c.Check(agentVersion, gc.Equals, version.MustParse(test.expectVersion))
   363  
   364  		for _, uploaded := range test.expectUploaded {
   365  			// Substitute latest LTS for placeholder in expected series for uploaded tools
   366  			uploaded = strings.Replace(uploaded, "%LTS%", config.LatestLtsSeries(), 1)
   367  			vers := version.MustParseBinary(uploaded)
   368  			s.checkToolsUploaded(c, vers, agentVersion)
   369  		}
   370  	}
   371  }
   372  
   373  func (s *UpgradeJujuSuite) checkToolsUploaded(c *gc.C, vers version.Binary, agentVersion version.Number) {
   374  	storage, err := s.State.ToolsStorage()
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	defer storage.Close()
   377  	_, r, err := storage.Tools(vers)
   378  	if !c.Check(err, jc.ErrorIsNil) {
   379  		return
   380  	}
   381  	data, err := ioutil.ReadAll(r)
   382  	r.Close()
   383  	c.Check(err, jc.ErrorIsNil)
   384  	expectContent := version.Current
   385  	expectContent.Number = agentVersion
   386  	checkToolsContent(c, data, "jujud contents "+expectContent.String())
   387  }
   388  
   389  func checkToolsContent(c *gc.C, data []byte, uploaded string) {
   390  	zr, err := gzip.NewReader(bytes.NewReader(data))
   391  	c.Check(err, jc.ErrorIsNil)
   392  	defer zr.Close()
   393  	tr := tar.NewReader(zr)
   394  	found := false
   395  	for {
   396  		hdr, err := tr.Next()
   397  		if err == io.EOF {
   398  			break
   399  		}
   400  		c.Check(err, jc.ErrorIsNil)
   401  		if strings.ContainsAny(hdr.Name, "/\\") {
   402  			c.Fail()
   403  		}
   404  		if hdr.Typeflag != tar.TypeReg {
   405  			c.Fail()
   406  		}
   407  		content, err := ioutil.ReadAll(tr)
   408  		c.Check(err, jc.ErrorIsNil)
   409  		c.Check(string(content), gc.Equals, uploaded)
   410  		found = true
   411  	}
   412  	c.Check(found, jc.IsTrue)
   413  }
   414  
   415  // JujuConnSuite very helpfully uploads some default
   416  // tools to the environment's storage. We don't want
   417  // 'em there; but we do want a consistent default-series
   418  // in the environment state.
   419  func (s *UpgradeJujuSuite) Reset(c *gc.C) {
   420  	s.JujuConnSuite.Reset(c)
   421  	envtesting.RemoveTools(c, s.DefaultToolsStorage, s.Environ.Config().AgentStream())
   422  	updateAttrs := map[string]interface{}{
   423  		"default-series": "raring",
   424  		"agent-version":  "1.2.3",
   425  	}
   426  	err := s.State.UpdateEnvironConfig(updateAttrs, nil, nil)
   427  	c.Assert(err, jc.ErrorIsNil)
   428  	s.PatchValue(&sync.BuildToolsTarball, toolstesting.GetMockBuildTools(c))
   429  
   430  	// Set API host ports so FindTools works.
   431  	hostPorts := [][]network.HostPort{
   432  		network.NewHostPorts(1234, "0.1.2.3"),
   433  	}
   434  	err = s.State.SetAPIHostPorts(hostPorts)
   435  	c.Assert(err, jc.ErrorIsNil)
   436  
   437  	s.CmdBlockHelper = NewCmdBlockHelper(s.APIState)
   438  	c.Assert(s.CmdBlockHelper, gc.NotNil)
   439  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
   440  }
   441  
   442  func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealUpload(c *gc.C) {
   443  	s.Reset(c)
   444  	cmd := envcmd.Wrap(&UpgradeJujuCommand{})
   445  	_, err := coretesting.RunCommand(c, cmd, "--upload-tools")
   446  	c.Assert(err, jc.ErrorIsNil)
   447  	vers := version.Current
   448  	vers.Build = 1
   449  	s.checkToolsUploaded(c, vers, vers.Number)
   450  }
   451  
   452  func (s *UpgradeJujuSuite) TestBlockUpgradeJujuWithRealUpload(c *gc.C) {
   453  	s.Reset(c)
   454  	cmd := envcmd.Wrap(&UpgradeJujuCommand{})
   455  	// Block operation
   456  	s.BlockAllChanges(c, "TestBlockUpgradeJujuWithRealUpload")
   457  	_, err := coretesting.RunCommand(c, cmd, "--upload-tools")
   458  	s.AssertBlocked(c, err, ".*TestBlockUpgradeJujuWithRealUpload.*")
   459  }
   460  
   461  type DryRunTest struct {
   462  	about             string
   463  	cmdArgs           []string
   464  	tools             []string
   465  	currentVersion    string
   466  	agentVersion      string
   467  	expectedCmdOutput string
   468  }
   469  
   470  func (s *UpgradeJujuSuite) TestUpgradeDryRun(c *gc.C) {
   471  	tests := []DryRunTest{
   472  		{
   473  			about:          "dry run outputs and doesn't change anything when uploading tools",
   474  			cmdArgs:        []string{"--upload-tools", "--dry-run"},
   475  			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"},
   476  			currentVersion: "2.0.0-quantal-amd64",
   477  			agentVersion:   "2.0.0",
   478  			expectedCmdOutput: `available tools:
   479      2.1-dev1-quantal-amd64
   480      2.1.0-quantal-amd64
   481      2.1.2-quantal-i386
   482      2.1.3-quantal-amd64
   483      2.2.3-quantal-amd64
   484  best version:
   485      2.1.3
   486  upgrade to this version by running
   487      juju upgrade-juju --version="2.1.3"
   488  `,
   489  		},
   490  		{
   491  			about:          "dry run outputs and doesn't change anything",
   492  			cmdArgs:        []string{"--dry-run"},
   493  			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"},
   494  			currentVersion: "2.0.0-quantal-amd64",
   495  			agentVersion:   "2.0.0",
   496  			expectedCmdOutput: `available tools:
   497      2.1-dev1-quantal-amd64
   498      2.1.0-quantal-amd64
   499      2.1.2-quantal-i386
   500      2.1.3-quantal-amd64
   501      2.2.3-quantal-amd64
   502  best version:
   503      2.1.3
   504  upgrade to this version by running
   505      juju upgrade-juju --version="2.1.3"
   506  `,
   507  		},
   508  		{
   509  			about:          "dry run ignores unknown series",
   510  			cmdArgs:        []string{"--dry-run"},
   511  			tools:          []string{"2.1.0-quantal-amd64", "2.1.2-quantal-i386", "2.1.3-quantal-amd64", "1.2.3-myawesomeseries-amd64"},
   512  			currentVersion: "2.0.0-quantal-amd64",
   513  			agentVersion:   "2.0.0",
   514  			expectedCmdOutput: `available tools:
   515      2.1.0-quantal-amd64
   516      2.1.2-quantal-i386
   517      2.1.3-quantal-amd64
   518  best version:
   519      2.1.3
   520  upgrade to this version by running
   521      juju upgrade-juju --version="2.1.3"
   522  `,
   523  		},
   524  	}
   525  
   526  	for i, test := range tests {
   527  		c.Logf("\ntest %d: %s", i, test.about)
   528  		s.Reset(c)
   529  		tools.DefaultBaseURL = ""
   530  
   531  		s.PatchValue(&version.Current, version.MustParseBinary(test.currentVersion))
   532  		com := &UpgradeJujuCommand{}
   533  		err := coretesting.InitCommand(envcmd.Wrap(com), test.cmdArgs)
   534  		c.Assert(err, jc.ErrorIsNil)
   535  		toolsDir := c.MkDir()
   536  		updateAttrs := map[string]interface{}{
   537  			"agent-version":      test.agentVersion,
   538  			"agent-metadata-url": "file://" + toolsDir + "/tools",
   539  		}
   540  
   541  		err = s.State.UpdateEnvironConfig(updateAttrs, nil, nil)
   542  		c.Assert(err, jc.ErrorIsNil)
   543  		versions := make([]version.Binary, len(test.tools))
   544  		for i, v := range test.tools {
   545  			versions[i], err = version.ParseBinary(v)
   546  			if err != nil {
   547  				c.Assert(err, jc.Satisfies, version.IsUnknownOSForSeriesError)
   548  			}
   549  		}
   550  		if len(versions) > 0 {
   551  			stor, err := filestorage.NewFileStorageWriter(toolsDir)
   552  			c.Assert(err, jc.ErrorIsNil)
   553  			envtesting.MustUploadFakeToolsVersions(stor, s.Environ.Config().AgentStream(), versions...)
   554  		}
   555  
   556  		ctx := coretesting.Context(c)
   557  		err = com.Run(ctx)
   558  		c.Assert(err, jc.ErrorIsNil)
   559  
   560  		// Check agent version doesn't change
   561  		cfg, err := s.State.EnvironConfig()
   562  		c.Assert(err, jc.ErrorIsNil)
   563  		agentVer, ok := cfg.AgentVersion()
   564  		c.Assert(ok, jc.IsTrue)
   565  		c.Assert(agentVer, gc.Equals, version.MustParse(test.agentVersion))
   566  		output := coretesting.Stderr(ctx)
   567  		c.Assert(output, gc.Equals, test.expectedCmdOutput)
   568  	}
   569  }
   570  
   571  func (s *UpgradeJujuSuite) TestUpgradeUnknownSeriesInStreams(c *gc.C) {
   572  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   573  	fakeAPI.addTools("2.1.0-weird-amd64")
   574  	fakeAPI.patch(s)
   575  
   576  	cmd := &UpgradeJujuCommand{}
   577  	err := coretesting.InitCommand(envcmd.Wrap(cmd), []string{})
   578  	c.Assert(err, jc.ErrorIsNil)
   579  
   580  	err = cmd.Run(coretesting.Context(c))
   581  	c.Assert(err, gc.IsNil)
   582  
   583  	// ensure find tools was called
   584  	c.Assert(fakeAPI.findToolsCalled, jc.IsTrue)
   585  	c.Assert(fakeAPI.tools, gc.DeepEquals, []string{"2.1.0-weird-amd64", fakeAPI.nextVersion.String()})
   586  }
   587  
   588  func (s *UpgradeJujuSuite) TestUpgradeInProgress(c *gc.C) {
   589  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   590  	fakeAPI.setVersionErr = &params.Error{
   591  		Message: "a message from the server about the problem",
   592  		Code:    params.CodeUpgradeInProgress,
   593  	}
   594  	fakeAPI.patch(s)
   595  	cmd := &UpgradeJujuCommand{}
   596  	err := coretesting.InitCommand(envcmd.Wrap(cmd), []string{})
   597  	c.Assert(err, jc.ErrorIsNil)
   598  
   599  	err = cmd.Run(coretesting.Context(c))
   600  	c.Assert(err, gc.ErrorMatches, "a message from the server about the problem\n"+
   601  		"\n"+
   602  		"Please wait for the upgrade to complete or if there was a problem with\n"+
   603  		"the last upgrade that has been resolved, consider running the\n"+
   604  		"upgrade-juju command with the --reset-previous-upgrade flag.",
   605  	)
   606  }
   607  
   608  func (s *UpgradeJujuSuite) TestBlockUpgradeInProgress(c *gc.C) {
   609  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   610  	fakeAPI.setVersionErr = common.ErrOperationBlocked("The operation has been blocked.")
   611  	fakeAPI.patch(s)
   612  	cmd := &UpgradeJujuCommand{}
   613  	err := coretesting.InitCommand(envcmd.Wrap(cmd), []string{})
   614  	c.Assert(err, jc.ErrorIsNil)
   615  
   616  	// Block operation
   617  	s.BlockAllChanges(c, "TestBlockUpgradeInProgress")
   618  	err = cmd.Run(coretesting.Context(c))
   619  	s.AssertBlocked(c, err, ".*To unblock changes.*")
   620  }
   621  
   622  func (s *UpgradeJujuSuite) TestResetPreviousUpgrade(c *gc.C) {
   623  	fakeAPI := NewFakeUpgradeJujuAPI(c, s.State)
   624  	fakeAPI.patch(s)
   625  
   626  	ctx := coretesting.Context(c)
   627  	var stdin bytes.Buffer
   628  	ctx.Stdin = &stdin
   629  
   630  	run := func(answer string, expect bool, args ...string) {
   631  		stdin.Reset()
   632  		if answer != "" {
   633  			stdin.WriteString(answer)
   634  		}
   635  
   636  		fakeAPI.reset()
   637  
   638  		cmd := &UpgradeJujuCommand{}
   639  		err := coretesting.InitCommand(envcmd.Wrap(cmd),
   640  			append([]string{"--reset-previous-upgrade"}, args...))
   641  		c.Assert(err, jc.ErrorIsNil)
   642  		err = cmd.Run(ctx)
   643  		if expect {
   644  			c.Assert(err, jc.ErrorIsNil)
   645  		} else {
   646  			c.Assert(err, gc.ErrorMatches, "previous upgrade not reset and no new upgrade triggered")
   647  		}
   648  
   649  		c.Assert(fakeAPI.abortCurrentUpgradeCalled, gc.Equals, expect)
   650  		expectedVersion := version.Number{}
   651  		if expect {
   652  			expectedVersion = fakeAPI.nextVersion.Number
   653  		}
   654  		c.Assert(fakeAPI.setVersionCalledWith, gc.Equals, expectedVersion)
   655  	}
   656  
   657  	const expectUpgrade = true
   658  	const expectNoUpgrade = false
   659  
   660  	// EOF on stdin - equivalent to answering no.
   661  	run("", expectNoUpgrade)
   662  
   663  	// -y on command line - no confirmation required
   664  	run("", expectUpgrade, "-y")
   665  
   666  	// --yes on command line - no confirmation required
   667  	run("", expectUpgrade, "--yes")
   668  
   669  	// various ways of saying "yes" to the prompt
   670  	for _, answer := range []string{"y", "Y", "yes", "YES"} {
   671  		run(answer, expectUpgrade)
   672  	}
   673  
   674  	// various ways of saying "no" to the prompt
   675  	for _, answer := range []string{"n", "N", "no", "foo"} {
   676  		run(answer, expectNoUpgrade)
   677  	}
   678  }
   679  
   680  func NewFakeUpgradeJujuAPI(c *gc.C, st *state.State) *fakeUpgradeJujuAPI {
   681  	nextVersion := version.Current
   682  	nextVersion.Minor++
   683  	return &fakeUpgradeJujuAPI{
   684  		c:           c,
   685  		st:          st,
   686  		nextVersion: nextVersion,
   687  	}
   688  }
   689  
   690  type fakeUpgradeJujuAPI struct {
   691  	c                         *gc.C
   692  	st                        *state.State
   693  	nextVersion               version.Binary
   694  	setVersionErr             error
   695  	abortCurrentUpgradeCalled bool
   696  	setVersionCalledWith      version.Number
   697  	tools                     []string
   698  	findToolsCalled           bool
   699  }
   700  
   701  func (a *fakeUpgradeJujuAPI) reset() {
   702  	a.setVersionErr = nil
   703  	a.abortCurrentUpgradeCalled = false
   704  	a.setVersionCalledWith = version.Number{}
   705  	a.tools = []string{}
   706  	a.findToolsCalled = false
   707  }
   708  
   709  func (a *fakeUpgradeJujuAPI) patch(s *UpgradeJujuSuite) {
   710  	s.PatchValue(&getUpgradeJujuAPI, func(*UpgradeJujuCommand) (upgradeJujuAPI, error) {
   711  		return a, nil
   712  	})
   713  }
   714  
   715  func (a *fakeUpgradeJujuAPI) addTools(tools ...string) {
   716  	for _, tool := range tools {
   717  		a.tools = append(a.tools, tool)
   718  	}
   719  }
   720  
   721  func (a *fakeUpgradeJujuAPI) EnvironmentGet() (map[string]interface{}, error) {
   722  	config, err := a.st.EnvironConfig()
   723  	if err != nil {
   724  		return make(map[string]interface{}), err
   725  	}
   726  	return config.AllAttrs(), nil
   727  }
   728  
   729  func (a *fakeUpgradeJujuAPI) FindTools(majorVersion, minorVersion int, series, arch string) (
   730  	result params.FindToolsResult, err error,
   731  ) {
   732  	a.findToolsCalled = true
   733  	a.tools = append(a.tools, a.nextVersion.String())
   734  	tools := toolstesting.MakeTools(a.c, a.c.MkDir(), "released", a.tools)
   735  	return params.FindToolsResult{
   736  		List:  tools,
   737  		Error: nil,
   738  	}, nil
   739  }
   740  
   741  func (a *fakeUpgradeJujuAPI) UploadTools(r io.Reader, vers version.Binary, additionalSeries ...string) (
   742  	*coretools.Tools, error,
   743  ) {
   744  	panic("not implemented")
   745  }
   746  
   747  func (a *fakeUpgradeJujuAPI) AbortCurrentUpgrade() error {
   748  	a.abortCurrentUpgradeCalled = true
   749  	return nil
   750  }
   751  
   752  func (a *fakeUpgradeJujuAPI) SetEnvironAgentVersion(v version.Number) error {
   753  	a.setVersionCalledWith = v
   754  	return a.setVersionErr
   755  }
   756  
   757  func (a *fakeUpgradeJujuAPI) Close() error {
   758  	return nil
   759  }