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