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