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