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