github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/upgradecharm_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/juju/cmd"
    16  	"github.com/juju/cmd/cmdtesting"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/charm.v6"
    23  	charmresource "gopkg.in/juju/charm.v6/resource"
    24  	"gopkg.in/juju/charmrepo.v3"
    25  	csclientparams "gopkg.in/juju/charmrepo.v3/csclient/params"
    26  	"gopkg.in/juju/charmstore.v5"
    27  	"gopkg.in/juju/names.v2"
    28  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    29  	"gopkg.in/macaroon.v2-unstable"
    30  
    31  	"github.com/juju/juju/api"
    32  	"github.com/juju/juju/api/application"
    33  	"github.com/juju/juju/api/base"
    34  	"github.com/juju/juju/api/charms"
    35  	"github.com/juju/juju/apiserver/params"
    36  	jujucharmstore "github.com/juju/juju/charmstore"
    37  	"github.com/juju/juju/cmd/modelcmd"
    38  	"github.com/juju/juju/core/instance"
    39  	"github.com/juju/juju/core/model"
    40  	jujutesting "github.com/juju/juju/juju/testing"
    41  	"github.com/juju/juju/jujuclient"
    42  	"github.com/juju/juju/network"
    43  	"github.com/juju/juju/resource/resourceadapters"
    44  	"github.com/juju/juju/rpc"
    45  	"github.com/juju/juju/state"
    46  	"github.com/juju/juju/storage"
    47  	"github.com/juju/juju/testcharms"
    48  	coretesting "github.com/juju/juju/testing"
    49  )
    50  
    51  type UpgradeCharmSuite struct {
    52  	testing.IsolationSuite
    53  	testing.Stub
    54  
    55  	deployResources   resourceadapters.DeployResourcesFunc
    56  	resolveCharm      ResolveCharmFunc
    57  	resolvedCharmURL  *charm.URL
    58  	apiConnection     mockAPIConnection
    59  	charmAdder        mockCharmAdder
    60  	charmClient       mockCharmClient
    61  	charmAPIClient    mockCharmAPIClient
    62  	modelConfigGetter mockModelConfigGetter
    63  	resourceLister    mockResourceLister
    64  	cmd               cmd.Command
    65  }
    66  
    67  var _ = gc.Suite(&UpgradeCharmSuite{})
    68  
    69  func (s *UpgradeCharmSuite) SetUpTest(c *gc.C) {
    70  	s.IsolationSuite.SetUpTest(c)
    71  	s.Stub.ResetCalls()
    72  
    73  	// Create persistent cookies in a temporary location.
    74  	cookieFile := filepath.Join(c.MkDir(), "cookies")
    75  	s.PatchEnvironment("JUJU_COOKIEFILE", cookieFile)
    76  
    77  	s.deployResources = func(
    78  		applicationID string,
    79  		chID jujucharmstore.CharmID,
    80  		csMac *macaroon.Macaroon,
    81  		filesAndRevisions map[string]string,
    82  		resources map[string]charmresource.Meta,
    83  		conn base.APICallCloser,
    84  	) (ids map[string]string, err error) {
    85  		s.AddCall("DeployResources", applicationID, chID, csMac, filesAndRevisions, resources, conn)
    86  		return nil, s.NextErr()
    87  	}
    88  
    89  	s.resolveCharm = func(
    90  		resolveWithChannel func(*charm.URL) (*charm.URL, csclientparams.Channel, []string, error),
    91  		url *charm.URL,
    92  	) (*charm.URL, csclientparams.Channel, []string, error) {
    93  		s.AddCall("ResolveCharm", resolveWithChannel, url)
    94  		if err := s.NextErr(); err != nil {
    95  			return nil, csclientparams.NoChannel, nil, err
    96  		}
    97  		return s.resolvedCharmURL, csclientparams.StableChannel, []string{"quantal"}, nil
    98  	}
    99  
   100  	currentCharmURL := charm.MustParseURL("cs:quantal/foo-1")
   101  	latestCharmURL := charm.MustParseURL("cs:quantal/foo-2")
   102  	s.resolvedCharmURL = latestCharmURL
   103  
   104  	s.apiConnection = mockAPIConnection{
   105  		bestFacadeVersion: 2,
   106  		serverVersion: &version.Number{
   107  			Major: 1,
   108  			Minor: 2,
   109  			Patch: 3,
   110  		},
   111  	}
   112  	s.charmAdder = mockCharmAdder{}
   113  	s.charmClient = mockCharmClient{
   114  		charmInfo: &charms.CharmInfo{
   115  			Meta: &charm.Meta{},
   116  		},
   117  	}
   118  	s.charmAPIClient = mockCharmAPIClient{charmURL: currentCharmURL}
   119  	s.modelConfigGetter = mockModelConfigGetter{}
   120  	s.resourceLister = mockResourceLister{}
   121  
   122  	store := jujuclient.NewMemStore()
   123  	store.CurrentControllerName = "foo"
   124  	store.Controllers["foo"] = jujuclient.ControllerDetails{
   125  		APIEndpoints: []string{"0.1.2.3:1234"},
   126  	}
   127  	store.Models["foo"] = &jujuclient.ControllerModels{
   128  		CurrentModel: "admin/bar",
   129  		Models:       map[string]jujuclient.ModelDetails{"admin/bar": {ModelGeneration: model.GenerationNext}},
   130  	}
   131  	apiOpen := func(*api.Info, api.DialOpts) (api.Connection, error) {
   132  		s.AddCall("OpenAPI")
   133  		return &s.apiConnection, nil
   134  	}
   135  
   136  	s.cmd = NewUpgradeCharmCommandForTest(
   137  		store,
   138  		apiOpen,
   139  		s.deployResources,
   140  		s.resolveCharm,
   141  		func(conn api.Connection, bakeryClient *httpbakery.Client, csURL string, channel csclientparams.Channel) CharmAdder {
   142  			s.AddCall("NewCharmAdder", conn, bakeryClient, csURL, channel)
   143  			s.PopNoErr()
   144  			return &s.charmAdder
   145  		},
   146  		func(conn base.APICallCloser) CharmClient {
   147  			s.AddCall("NewCharmClient", conn)
   148  			s.PopNoErr()
   149  			return &s.charmClient
   150  		},
   151  		func(conn base.APICallCloser) CharmAPIClient {
   152  			s.AddCall("NewCharmAPIClient", conn)
   153  			s.PopNoErr()
   154  			return &s.charmAPIClient
   155  		},
   156  		func(conn base.APICallCloser) ModelConfigGetter {
   157  			s.AddCall("NewModelConfigGetter", conn)
   158  			return &s.modelConfigGetter
   159  		},
   160  		func(conn base.APICallCloser) (ResourceLister, error) {
   161  			s.AddCall("NewResourceLister", conn)
   162  			return &s.resourceLister, s.NextErr()
   163  		},
   164  		func(conn base.APICallCloser) (string, error) {
   165  			s.AddCall("CharmStoreURLGetter", conn)
   166  			return "testing.api.charmstore", s.NextErr()
   167  		},
   168  	)
   169  }
   170  
   171  func (s *UpgradeCharmSuite) runUpgradeCharm(c *gc.C, args ...string) (*cmd.Context, error) {
   172  	return cmdtesting.RunCommand(c, s.cmd, args...)
   173  }
   174  
   175  func (s *UpgradeCharmSuite) TestStorageConstraints(c *gc.C) {
   176  	_, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz")
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	s.charmAPIClient.CheckCallNames(c, "GetCharmURL", "Get", "SetCharm")
   179  
   180  	s.charmAPIClient.CheckCall(c, 2, "SetCharm", model.GenerationNext, application.SetCharmConfig{
   181  		ApplicationName: "foo",
   182  		CharmID: jujucharmstore.CharmID{
   183  			URL:     s.resolvedCharmURL,
   184  			Channel: csclientparams.StableChannel,
   185  		},
   186  		StorageConstraints: map[string]storage.Constraints{
   187  			"bar": {Pool: "baz", Count: 1},
   188  		},
   189  	})
   190  }
   191  
   192  func (s *UpgradeCharmSuite) TestUseConfiguredCharmStoreURL(c *gc.C) {
   193  	_, err := s.runUpgradeCharm(c, "foo")
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	var csURL string
   196  	for _, call := range s.Calls() {
   197  		if call.FuncName == "NewCharmAdder" {
   198  			csURL = call.Args[2].(string)
   199  			break
   200  		}
   201  	}
   202  	c.Assert(csURL, gc.Equals, "testing.api.charmstore")
   203  }
   204  
   205  func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersion(c *gc.C) {
   206  	s.apiConnection.bestFacadeVersion = 1
   207  	_, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz")
   208  	c.Assert(err, gc.ErrorMatches,
   209  		"updating storage constraints at upgrade-charm time is not supported by server version 1.2.3")
   210  }
   211  
   212  func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersionNoServerVersion(c *gc.C) {
   213  	s.apiConnection.bestFacadeVersion = 1
   214  	s.apiConnection.serverVersion = nil
   215  	_, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz")
   216  	c.Assert(err, gc.ErrorMatches,
   217  		"updating storage constraints at upgrade-charm time is not supported by this server")
   218  }
   219  
   220  func (s *UpgradeCharmSuite) TestConfigSettings(c *gc.C) {
   221  	tempdir := c.MkDir()
   222  	configFile := filepath.Join(tempdir, "config.yaml")
   223  	err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644)
   224  	c.Assert(err, jc.ErrorIsNil)
   225  
   226  	_, err = s.runUpgradeCharm(c, "foo", "--config", configFile)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	s.charmAPIClient.CheckCallNames(c, "GetCharmURL", "Get", "SetCharm")
   229  
   230  	s.charmAPIClient.CheckCall(c, 2, "SetCharm", model.GenerationNext, application.SetCharmConfig{
   231  		ApplicationName: "foo",
   232  		CharmID: jujucharmstore.CharmID{
   233  			URL:     s.resolvedCharmURL,
   234  			Channel: csclientparams.StableChannel,
   235  		},
   236  		ConfigSettingsYAML: "foo:{}",
   237  	})
   238  }
   239  
   240  func (s *UpgradeCharmSuite) TestConfigSettingsMinFacadeVersion(c *gc.C) {
   241  	tempdir := c.MkDir()
   242  	configFile := filepath.Join(tempdir, "config.yaml")
   243  	err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644)
   244  	c.Assert(err, jc.ErrorIsNil)
   245  
   246  	s.apiConnection.bestFacadeVersion = 1
   247  	_, err = s.runUpgradeCharm(c, "foo", "--config", configFile)
   248  	c.Assert(err, gc.ErrorMatches,
   249  		"updating config at upgrade-charm time is not supported by server version 1.2.3")
   250  }
   251  
   252  type UpgradeCharmErrorsStateSuite struct {
   253  	jujutesting.RepoSuite
   254  	handler charmstore.HTTPCloseHandler
   255  	srv     *httptest.Server
   256  }
   257  
   258  func (s *UpgradeCharmErrorsStateSuite) SetUpTest(c *gc.C) {
   259  	s.RepoSuite.SetUpTest(c)
   260  	// Set up the charm store testing server.
   261  	handler, err := charmstore.NewServer(s.Session.DB("juju-testing"), nil, "", charmstore.ServerParams{
   262  		AuthUsername: "test-user",
   263  		AuthPassword: "test-password",
   264  	}, charmstore.V5)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	s.handler = handler
   267  	s.srv = httptest.NewServer(handler)
   268  	s.AddCleanup(func(*gc.C) {
   269  		s.handler.Close()
   270  		s.srv.Close()
   271  	})
   272  
   273  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
   274  	s.PatchValue(&getCharmStoreAPIURL, func(base.APICallCloser) (string, error) {
   275  		return s.srv.URL, nil
   276  	})
   277  }
   278  
   279  var _ = gc.Suite(&UpgradeCharmErrorsStateSuite{})
   280  
   281  func runUpgradeCharm(c *gc.C, args ...string) error {
   282  	_, err := cmdtesting.RunCommand(c, NewUpgradeCharmCommand(), args...)
   283  	return err
   284  }
   285  
   286  func (s *UpgradeCharmErrorsStateSuite) TestInvalidArgs(c *gc.C) {
   287  	err := runUpgradeCharm(c)
   288  	c.Assert(err, gc.ErrorMatches, "no application specified")
   289  	err = runUpgradeCharm(c, "invalid:name")
   290  	c.Assert(err, gc.ErrorMatches, `invalid application name "invalid:name"`)
   291  	err = runUpgradeCharm(c, "foo", "bar")
   292  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
   293  }
   294  
   295  func (s *UpgradeCharmErrorsStateSuite) TestInvalidApplication(c *gc.C) {
   296  	err := runUpgradeCharm(c, "phony")
   297  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   298  		Message: `application "phony" not found`,
   299  		Code:    "not found",
   300  	})
   301  }
   302  
   303  func (s *UpgradeCharmErrorsStateSuite) deployApplication(c *gc.C) {
   304  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
   305  	err := runDeploy(c, ch, "riak", "--series", "quantal")
   306  	c.Assert(err, jc.ErrorIsNil)
   307  }
   308  
   309  func (s *UpgradeCharmErrorsStateSuite) TestInvalidSwitchURL(c *gc.C) {
   310  	s.deployApplication(c)
   311  	err := runUpgradeCharm(c, "riak", "--switch=blah")
   312  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:blah": charm or bundle not found`)
   313  	err = runUpgradeCharm(c, "riak", "--switch=cs:missing/one")
   314  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:missing/one": charm not found`)
   315  	// TODO(dimitern): add tests with incompatible charms
   316  }
   317  
   318  func (s *UpgradeCharmErrorsStateSuite) TestNoPathFails(c *gc.C) {
   319  	s.deployApplication(c)
   320  	err := runUpgradeCharm(c, "riak")
   321  	c.Assert(err, gc.ErrorMatches, "upgrading a local charm requires either --path or --switch")
   322  }
   323  
   324  func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndRevisionFails(c *gc.C) {
   325  	s.deployApplication(c)
   326  	err := runUpgradeCharm(c, "riak", "--switch=riak", "--revision=2")
   327  	c.Assert(err, gc.ErrorMatches, "--switch and --revision are mutually exclusive")
   328  }
   329  
   330  func (s *UpgradeCharmErrorsStateSuite) TestPathAndRevisionFails(c *gc.C) {
   331  	s.deployApplication(c)
   332  	err := runUpgradeCharm(c, "riak", "--path=foo", "--revision=2")
   333  	c.Assert(err, gc.ErrorMatches, "--path and --revision are mutually exclusive")
   334  }
   335  
   336  func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndPathFails(c *gc.C) {
   337  	s.deployApplication(c)
   338  	err := runUpgradeCharm(c, "riak", "--switch=riak", "--path=foo")
   339  	c.Assert(err, gc.ErrorMatches, "--switch and --path are mutually exclusive")
   340  }
   341  
   342  func (s *UpgradeCharmErrorsStateSuite) TestInvalidRevision(c *gc.C) {
   343  	s.deployApplication(c)
   344  	err := runUpgradeCharm(c, "riak", "--revision=blah")
   345  	c.Assert(err, gc.ErrorMatches, `invalid value "blah" for option --revision: strconv.(ParseInt|Atoi): parsing "blah": invalid syntax`)
   346  }
   347  
   348  type BaseUpgradeCharmStateSuite struct{}
   349  
   350  type UpgradeCharmSuccessStateSuite struct {
   351  	BaseUpgradeCharmStateSuite
   352  	jujutesting.RepoSuite
   353  	coretesting.CmdBlockHelper
   354  	path string
   355  	riak *state.Application
   356  }
   357  
   358  func (s *BaseUpgradeCharmStateSuite) assertUpgraded(c *gc.C, riak *state.Application, revision int, forced bool) *charm.URL {
   359  	err := riak.Refresh()
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	ch, force, err := riak.Charm()
   362  	c.Assert(err, jc.ErrorIsNil)
   363  	c.Assert(ch.Revision(), gc.Equals, revision)
   364  	c.Assert(force, gc.Equals, forced)
   365  	return ch.URL()
   366  }
   367  
   368  var _ = gc.Suite(&UpgradeCharmSuccessStateSuite{})
   369  
   370  func (s *UpgradeCharmSuccessStateSuite) SetUpTest(c *gc.C) {
   371  	s.RepoSuite.SetUpTest(c)
   372  	s.path = testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
   373  	err := runDeploy(c, s.path, "--series", "quantal")
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	curl := charm.MustParseURL("local:quantal/riak-7")
   376  	s.riak, _ = s.RepoSuite.AssertApplication(c, "riak", curl, 1, 1)
   377  
   378  	_, forced, err := s.riak.Charm()
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	c.Assert(forced, jc.IsFalse)
   381  
   382  	s.CmdBlockHelper = coretesting.NewCmdBlockHelper(s.APIState)
   383  	c.Assert(s.CmdBlockHelper, gc.NotNil)
   384  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
   385  }
   386  
   387  func (s *UpgradeCharmSuccessStateSuite) assertLocalRevision(c *gc.C, revision int, path string) {
   388  	dir, err := charm.ReadCharmDir(path)
   389  	c.Assert(err, jc.ErrorIsNil)
   390  	c.Assert(dir.Revision(), gc.Equals, revision)
   391  }
   392  
   393  func (s *UpgradeCharmSuccessStateSuite) TestLocalRevisionUnchanged(c *gc.C) {
   394  	err := runUpgradeCharm(c, "riak", "--path", s.path)
   395  	c.Assert(err, jc.ErrorIsNil)
   396  	curl := s.assertUpgraded(c, s.riak, 8, false)
   397  	s.AssertCharmUploaded(c, curl)
   398  	// Even though the remote revision is bumped, the local one should
   399  	// be unchanged.
   400  	s.assertLocalRevision(c, 7, s.path)
   401  }
   402  
   403  func (s *UpgradeCharmSuccessStateSuite) TestBlockUpgradeCharm(c *gc.C) {
   404  	// Block operation
   405  	s.BlockAllChanges(c, "TestBlockUpgradeCharm")
   406  	err := runUpgradeCharm(c, "riak", "--path", s.path)
   407  	s.AssertBlocked(c, err, ".*TestBlockUpgradeCharm.*")
   408  }
   409  
   410  func (s *UpgradeCharmSuccessStateSuite) TestRespectsLocalRevisionWhenPossible(c *gc.C) {
   411  	dir, err := charm.ReadCharmDir(s.path)
   412  	c.Assert(err, jc.ErrorIsNil)
   413  	err = dir.SetDiskRevision(42)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  
   416  	err = runUpgradeCharm(c, "riak", "--path", s.path)
   417  	c.Assert(err, jc.ErrorIsNil)
   418  	curl := s.assertUpgraded(c, s.riak, 42, false)
   419  	s.AssertCharmUploaded(c, curl)
   420  	s.assertLocalRevision(c, 42, s.path)
   421  }
   422  
   423  func (s *UpgradeCharmSuccessStateSuite) TestForcedSeriesUpgrade(c *gc.C) {
   424  	path := testcharms.Repo.ClonedDirPath(c.MkDir(), "multi-series")
   425  	err := runDeploy(c, path, "multi-series", "--series", "precise")
   426  	c.Assert(err, jc.ErrorIsNil)
   427  	application, err := s.State.Application("multi-series")
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	ch, _, err := application.Charm()
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	c.Assert(ch.Revision(), gc.Equals, 1)
   432  
   433  	units, err := application.AllUnits()
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	c.Assert(units, gc.HasLen, 1)
   436  	unit := units[0]
   437  	tags := []names.UnitTag{unit.UnitTag()}
   438  	errs, err := s.APIState.UnitAssigner().AssignUnits(tags)
   439  	c.Assert(err, jc.ErrorIsNil)
   440  	c.Assert(errs, gc.DeepEquals, make([]error, len(units)))
   441  
   442  	// Overwrite the metadata.yaml to change the supported series.
   443  	metadataPath := filepath.Join(path, "metadata.yaml")
   444  	file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666)
   445  	if err != nil {
   446  		c.Fatal(errors.Annotate(err, "cannot open metadata.yaml for overwriting"))
   447  	}
   448  	defer file.Close()
   449  
   450  	metadata := strings.Join(
   451  		[]string{
   452  			`name: multi-series`,
   453  			`summary: "That's a dummy charm with multi-series."`,
   454  			`description: |`,
   455  			`    This is a longer description which`,
   456  			`    potentially contains multiple lines.`,
   457  			`series:`,
   458  			`    - trusty`,
   459  			`    - wily`,
   460  		},
   461  		"\n",
   462  	)
   463  	if _, err := file.WriteString(metadata); err != nil {
   464  		c.Fatal(errors.Annotate(err, "cannot write to metadata.yaml"))
   465  	}
   466  
   467  	err = runUpgradeCharm(c, "multi-series", "--path", path, "--force-series")
   468  	c.Assert(err, jc.ErrorIsNil)
   469  
   470  	err = application.Refresh()
   471  	c.Assert(err, jc.ErrorIsNil)
   472  
   473  	ch, force, err := application.Charm()
   474  	c.Assert(err, jc.ErrorIsNil)
   475  	c.Check(ch.Revision(), gc.Equals, 2)
   476  	c.Check(force, gc.Equals, false)
   477  }
   478  
   479  func (s *UpgradeCharmSuccessStateSuite) TestForcedLXDProfileUpgrade(c *gc.C) {
   480  	path := testcharms.Repo.ClonedDirPath(c.MkDir(), "lxd-profile-alt")
   481  	err := runDeploy(c, path, "lxd-profile-alt", "--to", "lxd")
   482  	c.Assert(err, jc.ErrorIsNil)
   483  	application, err := s.State.Application("lxd-profile-alt")
   484  	c.Assert(err, jc.ErrorIsNil)
   485  	ch, _, err := application.Charm()
   486  	c.Assert(err, jc.ErrorIsNil)
   487  	c.Assert(ch.Revision(), gc.Equals, 0)
   488  
   489  	units, err := application.AllUnits()
   490  	c.Assert(err, jc.ErrorIsNil)
   491  	c.Assert(units, gc.HasLen, 1)
   492  	unit := units[0]
   493  
   494  	container, err := s.State.AddMachineInsideNewMachine(
   495  		state.MachineTemplate{
   496  			Series: "bionic",
   497  			Jobs:   []state.MachineJob{state.JobHostUnits},
   498  		},
   499  		state.MachineTemplate{ // parent
   500  			Series: "bionic",
   501  			Jobs:   []state.MachineJob{state.JobHostUnits},
   502  		},
   503  		instance.LXD,
   504  	)
   505  	c.Assert(err, jc.ErrorIsNil)
   506  
   507  	err = unit.AssignToMachine(container)
   508  	c.Assert(err, jc.ErrorIsNil)
   509  
   510  	// Overwrite the lxd-profile.yaml to change the supported series.
   511  	lxdProfilePath := filepath.Join(path, "lxd-profile.yaml")
   512  	file, err := os.OpenFile(lxdProfilePath, os.O_TRUNC|os.O_RDWR, 0666)
   513  	if err != nil {
   514  		c.Fatal(errors.Annotate(err, "cannot open lxd-profile.yaml for overwriting"))
   515  	}
   516  	defer file.Close()
   517  
   518  	lxdProfile := `
   519  description: lxd profile for testing
   520  config:
   521    security.nesting: "true"
   522    security.privileged: "true"
   523    linux.kernel_modules: openvswitch,nbd,ip_tables,ip6_tables
   524    environment.http_proxy: ""
   525    boot.autostart.delay: 1
   526  devices: {}
   527  `
   528  	if _, err := file.WriteString(lxdProfile); err != nil {
   529  		c.Fatal(errors.Annotate(err, "cannot write to lxd-profile.yaml"))
   530  	}
   531  
   532  	err = runUpgradeCharm(c, "lxd-profile-alt", "--path", path)
   533  	c.Assert(err, gc.ErrorMatches, `invalid lxd-profile.yaml: contains config value "boot.autostart.delay"`)
   534  }
   535  
   536  func (s *UpgradeCharmSuccessStateSuite) TestInitWithResources(c *gc.C) {
   537  	testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   538  	dir := c.MkDir()
   539  
   540  	foopath := path.Join(dir, "foo")
   541  	barpath := path.Join(dir, "bar")
   542  	err := ioutil.WriteFile(foopath, []byte("foo"), 0600)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  	err = ioutil.WriteFile(barpath, []byte("bar"), 0600)
   545  	c.Assert(err, jc.ErrorIsNil)
   546  
   547  	res1 := fmt.Sprintf("foo=%s", foopath)
   548  	res2 := fmt.Sprintf("bar=%s", barpath)
   549  
   550  	d := upgradeCharmCommand{}
   551  	args := []string{"dummy", "--resource", res1, "--resource", res2}
   552  
   553  	err = cmdtesting.InitCommand(modelcmd.Wrap(&d), args)
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	c.Assert(d.Resources, gc.DeepEquals, map[string]string{
   556  		"foo": foopath,
   557  		"bar": barpath,
   558  	})
   559  }
   560  
   561  func (s *UpgradeCharmSuccessStateSuite) TestForcedUnitsUpgrade(c *gc.C) {
   562  	err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path)
   563  	c.Assert(err, jc.ErrorIsNil)
   564  	curl := s.assertUpgraded(c, s.riak, 8, true)
   565  	s.AssertCharmUploaded(c, curl)
   566  	// Local revision is not changed.
   567  	s.assertLocalRevision(c, 7, s.path)
   568  }
   569  
   570  func (s *UpgradeCharmSuccessStateSuite) TestBlockForcedUnitsUpgrade(c *gc.C) {
   571  	// Block operation
   572  	s.BlockAllChanges(c, "TestBlockForcedUpgrade")
   573  	err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path)
   574  	c.Assert(err, jc.ErrorIsNil)
   575  	curl := s.assertUpgraded(c, s.riak, 8, true)
   576  	s.AssertCharmUploaded(c, curl)
   577  	// Local revision is not changed.
   578  	s.assertLocalRevision(c, 7, s.path)
   579  }
   580  
   581  func (s *UpgradeCharmSuccessStateSuite) TestCharmPath(c *gc.C) {
   582  	myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak")
   583  
   584  	// Change the revision to 42 and upgrade to it with explicit revision.
   585  	err := ioutil.WriteFile(path.Join(myriakPath, "revision"), []byte("42"), 0644)
   586  	c.Assert(err, jc.ErrorIsNil)
   587  	err = runUpgradeCharm(c, "riak", "--path", myriakPath)
   588  	c.Assert(err, jc.ErrorIsNil)
   589  	curl := s.assertUpgraded(c, s.riak, 42, false)
   590  	c.Assert(curl.String(), gc.Equals, "local:quantal/riak-42")
   591  	s.assertLocalRevision(c, 42, myriakPath)
   592  }
   593  
   594  func (s *UpgradeCharmSuccessStateSuite) TestCharmPathNoRevUpgrade(c *gc.C) {
   595  	// Revision 7 is running to start with.
   596  	myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak")
   597  	s.assertLocalRevision(c, 7, myriakPath)
   598  	err := runUpgradeCharm(c, "riak", "--path", myriakPath)
   599  	c.Assert(err, jc.ErrorIsNil)
   600  	curl := s.assertUpgraded(c, s.riak, 8, false)
   601  	c.Assert(curl.String(), gc.Equals, "local:quantal/riak-8")
   602  }
   603  
   604  func (s *UpgradeCharmSuccessStateSuite) TestCharmPathDifferentNameFails(c *gc.C) {
   605  	myriakPath := testcharms.Repo.RenamedClonedDirPath(s.CharmsPath, "riak", "myriak")
   606  	metadataPath := filepath.Join(myriakPath, "metadata.yaml")
   607  	file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666)
   608  	if err != nil {
   609  		c.Fatal(errors.Annotate(err, "cannot open metadata.yaml"))
   610  	}
   611  	defer file.Close()
   612  
   613  	// Overwrite the metadata.yaml to contain a new name.
   614  	newMetadata := strings.Join([]string{`name: myriak`, `summary: ""`, `description: ""`}, "\n")
   615  	if _, err := file.WriteString(newMetadata); err != nil {
   616  		c.Fatal("cannot write to metadata.yaml")
   617  	}
   618  	err = runUpgradeCharm(c, "riak", "--path", myriakPath)
   619  	c.Assert(err, gc.ErrorMatches, `cannot upgrade "riak" to "myriak"`)
   620  }
   621  
   622  type UpgradeCharmCharmStoreStateSuite struct {
   623  	BaseUpgradeCharmStateSuite
   624  	charmStoreSuite
   625  }
   626  
   627  var _ = gc.Suite(&UpgradeCharmCharmStoreStateSuite{})
   628  
   629  var upgradeCharmAuthorizationTests = []struct {
   630  	about        string
   631  	uploadURL    string
   632  	switchURL    string
   633  	readPermUser string
   634  	expectError  string
   635  }{{
   636  	about:     "public charm, success",
   637  	uploadURL: "cs:~bob/trusty/wordpress1-10",
   638  	switchURL: "cs:~bob/trusty/wordpress1",
   639  }, {
   640  	about:     "public charm, fully resolved, success",
   641  	uploadURL: "cs:~bob/trusty/wordpress2-10",
   642  	switchURL: "cs:~bob/trusty/wordpress2-10",
   643  }, {
   644  	about:        "non-public charm, success",
   645  	uploadURL:    "cs:~bob/trusty/wordpress3-10",
   646  	switchURL:    "cs:~bob/trusty/wordpress3",
   647  	readPermUser: clientUserName,
   648  }, {
   649  	about:        "non-public charm, fully resolved, success",
   650  	uploadURL:    "cs:~bob/trusty/wordpress4-10",
   651  	switchURL:    "cs:~bob/trusty/wordpress4-10",
   652  	readPermUser: clientUserName,
   653  }, {
   654  	about:        "non-public charm, access denied",
   655  	uploadURL:    "cs:~bob/trusty/wordpress5-10",
   656  	switchURL:    "cs:~bob/trusty/wordpress5",
   657  	readPermUser: "bob",
   658  	expectError:  `cannot resolve charm URL "cs:~bob/trusty/wordpress5": cannot get "/~bob/trusty/wordpress5/meta/any\?include=id&include=supported-series&include=published": access denied for user "client-username"`,
   659  }, {
   660  	about:        "non-public charm, fully resolved, access denied",
   661  	uploadURL:    "cs:~bob/trusty/wordpress6-47",
   662  	switchURL:    "cs:~bob/trusty/wordpress6-47",
   663  	readPermUser: "bob",
   664  	expectError:  `cannot resolve charm URL "cs:~bob/trusty/wordpress6-47": cannot get "/~bob/trusty/wordpress6-47/meta/any\?include=id&include=supported-series&include=published": access denied for user "client-username"`,
   665  }}
   666  
   667  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmAuthorization(c *gc.C) {
   668  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/wordpress-0", "wordpress")
   669  	err := runDeploy(c, "cs:~other/trusty/wordpress-0")
   670  
   671  	riak, err := s.State.Application("wordpress")
   672  	c.Assert(err, jc.ErrorIsNil)
   673  	ch, forced, err := riak.Charm()
   674  	c.Assert(err, jc.ErrorIsNil)
   675  	c.Assert(ch.Revision(), gc.Equals, 0)
   676  	c.Assert(forced, jc.IsFalse)
   677  
   678  	unit, err := s.State.Unit("wordpress/0")
   679  	c.Assert(err, jc.ErrorIsNil)
   680  	tags := []names.UnitTag{unit.UnitTag()}
   681  	errs, err := s.APIState.UnitAssigner().AssignUnits(tags)
   682  	c.Assert(err, jc.ErrorIsNil)
   683  	c.Assert(errs, gc.DeepEquals, []error{nil})
   684  
   685  	c.Assert(err, jc.ErrorIsNil)
   686  	for i, test := range upgradeCharmAuthorizationTests {
   687  		c.Logf("test %d: %s", i, test.about)
   688  		url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress")
   689  		if test.readPermUser != "" {
   690  			s.changeReadPerm(c, url, test.readPermUser)
   691  		}
   692  		err := runUpgradeCharm(c, "wordpress", "--switch", test.switchURL)
   693  		if test.expectError != "" {
   694  			c.Assert(err, gc.ErrorMatches, test.expectError)
   695  			continue
   696  		}
   697  		c.Assert(err, jc.ErrorIsNil)
   698  
   699  		// Ensure we clean up the unit after the test
   700  		unit, err = s.State.Unit("wordpress/0")
   701  		c.Assert(err, jc.ErrorIsNil)
   702  		err = unit.RemoveUpgradeCharmProfileData()
   703  		c.Assert(err, jc.ErrorIsNil)
   704  	}
   705  }
   706  
   707  func (s *UpgradeCharmCharmStoreStateSuite) TestSwitch(c *gc.C) {
   708  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/riak-0", "riak")
   709  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-7", "riak")
   710  	err := runDeploy(c, "cs:~other/trusty/riak-0")
   711  	c.Assert(err, jc.ErrorIsNil)
   712  
   713  	riak, err := s.State.Application("riak")
   714  	c.Assert(err, jc.ErrorIsNil)
   715  	ch, forced, err := riak.Charm()
   716  	c.Assert(err, jc.ErrorIsNil)
   717  	c.Assert(ch.Revision(), gc.Equals, 0)
   718  	c.Assert(forced, jc.IsFalse)
   719  
   720  	unit, err := s.State.Unit("riak/0")
   721  	c.Assert(err, jc.ErrorIsNil)
   722  	tags := []names.UnitTag{unit.UnitTag()}
   723  	errs, err := s.APIState.UnitAssigner().AssignUnits(tags)
   724  	c.Assert(err, jc.ErrorIsNil)
   725  	c.Assert(errs, gc.DeepEquals, []error{nil})
   726  
   727  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak")
   728  	c.Assert(err, jc.ErrorIsNil)
   729  	curl := s.assertUpgraded(c, riak, 7, false)
   730  	c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-7")
   731  
   732  	// Now try the same with explicit revision - should fail.
   733  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-7")
   734  	c.Assert(err, gc.ErrorMatches, `already running specified charm "cs:~other/trusty/anotherriak-7"`)
   735  
   736  	// Change the revision to 42 and upgrade to it with explicit revision.
   737  	unit, err = s.State.Unit("riak/0")
   738  	c.Assert(err, jc.ErrorIsNil)
   739  	err = unit.RemoveUpgradeCharmProfileData()
   740  	c.Assert(err, jc.ErrorIsNil)
   741  
   742  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-42", "riak")
   743  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-42")
   744  	c.Assert(err, jc.ErrorIsNil)
   745  	curl = s.assertUpgraded(c, riak, 42, false)
   746  	c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-42")
   747  }
   748  
   749  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmWithChannel(c *gc.C) {
   750  	id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress")
   751  	err := runDeploy(c, "cs:~client-username/trusty/wordpress-0")
   752  	c.Assert(err, jc.ErrorIsNil)
   753  
   754  	unit, err := s.State.Unit("wordpress/0")
   755  	c.Assert(err, jc.ErrorIsNil)
   756  	tags := []names.UnitTag{unit.UnitTag()}
   757  	errs, err := s.APIState.UnitAssigner().AssignUnits(tags)
   758  	c.Assert(err, jc.ErrorIsNil)
   759  	c.Assert(errs, gc.DeepEquals, []error{nil})
   760  
   761  	// Upload a new revision of the charm, but publish it
   762  	// only to the beta channel.
   763  
   764  	id.Revision = 1
   765  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   766  	c.Assert(err, gc.IsNil)
   767  
   768  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil)
   769  	c.Assert(err, gc.IsNil)
   770  
   771  	err = runUpgradeCharm(c, "wordpress", "--channel", "beta")
   772  	c.Assert(err, gc.IsNil)
   773  
   774  	s.assertCharmsUploaded(c, "cs:~client-username/trusty/wordpress-0", "cs:~client-username/trusty/wordpress-1")
   775  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   776  		"wordpress": {charm: "cs:~client-username/trusty/wordpress-1", config: ch.Config().DefaultSettings()},
   777  	})
   778  }
   779  
   780  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmShouldRespectDeployedChannelByDefault(c *gc.C) {
   781  	id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress")
   782  
   783  	// publish charm to beta channel
   784  	id.Revision = 1
   785  	err := s.client.UploadCharmWithRevision(id, ch, -1)
   786  	c.Assert(err, gc.IsNil)
   787  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil)
   788  	c.Assert(err, gc.IsNil)
   789  
   790  	// deploy from beta channel
   791  	err = runDeploy(c, "cs:~client-username/trusty/wordpress-1")
   792  	c.Assert(err, jc.ErrorIsNil)
   793  
   794  	unit, err := s.State.Unit("wordpress/0")
   795  	c.Assert(err, jc.ErrorIsNil)
   796  	tags := []names.UnitTag{unit.UnitTag()}
   797  	errs, err := s.APIState.UnitAssigner().AssignUnits(tags)
   798  	c.Assert(err, jc.ErrorIsNil)
   799  	c.Assert(errs, gc.DeepEquals, []error{nil})
   800  
   801  	// publish revision 2 to stable channel
   802  	id.Revision = 2
   803  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   804  	c.Assert(err, gc.IsNil)
   805  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil)
   806  	c.Assert(err, gc.IsNil)
   807  
   808  	// publish revision 3 to beta channel
   809  	id.Revision = 3
   810  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   811  	c.Assert(err, gc.IsNil)
   812  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.StableChannel}, nil)
   813  	c.Assert(err, gc.IsNil)
   814  
   815  	// running upgrade charm without specifying a channel should use the
   816  	// beta channel by default, not the stable channel, since we originally deployed
   817  	// from beta
   818  	err = runUpgradeCharm(c, "wordpress")
   819  	c.Assert(err, gc.IsNil)
   820  
   821  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   822  		"wordpress": {charm: "cs:~client-username/trusty/wordpress-2", config: ch.Config().DefaultSettings()},
   823  	})
   824  }
   825  
   826  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeWithTermsNotSigned(c *gc.C) {
   827  	id, ch := testcharms.UploadCharm(c, s.client, "quantal/terms1-1", "terms1")
   828  	err := runDeploy(c, "quantal/terms1")
   829  	c.Assert(err, jc.ErrorIsNil)
   830  	id.Revision = id.Revision + 1
   831  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   832  	c.Assert(err, gc.IsNil)
   833  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.StableChannel}, nil)
   834  	c.Assert(err, gc.IsNil)
   835  	s.termsDischargerError = &httpbakery.Error{
   836  		Message: "term agreement required: term/1 term/2",
   837  		Code:    "term agreement required",
   838  	}
   839  	expectedError := `Declined: some terms require agreement. Try: "juju agree term/1 term/2"`
   840  	err = runUpgradeCharm(c, "terms1")
   841  	c.Assert(err, gc.ErrorMatches, expectedError)
   842  }
   843  
   844  type mockAPIConnection struct {
   845  	api.Connection
   846  	bestFacadeVersion int
   847  	serverVersion     *version.Number
   848  }
   849  
   850  func (m *mockAPIConnection) Addr() string {
   851  	return "0.1.2.3:1234"
   852  }
   853  
   854  func (m *mockAPIConnection) IPAddr() string {
   855  	return "0.1.2.3:1234"
   856  }
   857  
   858  func (m *mockAPIConnection) AuthTag() names.Tag {
   859  	return names.NewUserTag("testuser")
   860  }
   861  
   862  func (m *mockAPIConnection) PublicDNSName() string {
   863  	return ""
   864  }
   865  
   866  func (m *mockAPIConnection) APIHostPorts() [][]network.HostPort {
   867  	p, _ := network.ParseHostPorts(m.Addr())
   868  	return [][]network.HostPort{p}
   869  }
   870  
   871  func (m *mockAPIConnection) BestFacadeVersion(name string) int {
   872  	return m.bestFacadeVersion
   873  }
   874  
   875  func (m *mockAPIConnection) ServerVersion() (version.Number, bool) {
   876  	if m.serverVersion != nil {
   877  		return *m.serverVersion, true
   878  	}
   879  	return version.Number{}, false
   880  }
   881  
   882  func (*mockAPIConnection) Close() error {
   883  	return nil
   884  }
   885  
   886  type mockCharmAdder struct {
   887  	CharmAdder
   888  	testing.Stub
   889  }
   890  
   891  func (m *mockCharmAdder) AddCharm(curl *charm.URL, channel csclientparams.Channel, force bool) error {
   892  	m.MethodCall(m, "AddCharm", curl, channel, force)
   893  	return m.NextErr()
   894  }
   895  
   896  type mockCharmClient struct {
   897  	CharmClient
   898  	testing.Stub
   899  	charmInfo *charms.CharmInfo
   900  }
   901  
   902  func (m *mockCharmClient) CharmInfo(curl string) (*charms.CharmInfo, error) {
   903  	m.MethodCall(m, "CharmInfo", curl)
   904  	if err := m.NextErr(); err != nil {
   905  		return nil, err
   906  	}
   907  	return m.charmInfo, nil
   908  }
   909  
   910  type mockCharmAPIClient struct {
   911  	CharmAPIClient
   912  	testing.Stub
   913  	charmURL *charm.URL
   914  }
   915  
   916  func (m *mockCharmAPIClient) GetCharmURL(generation model.GenerationVersion, appName string) (*charm.URL, error) {
   917  	m.MethodCall(m, "GetCharmURL", generation, appName)
   918  	return m.charmURL, m.NextErr()
   919  }
   920  
   921  func (m *mockCharmAPIClient) SetCharm(generation model.GenerationVersion, cfg application.SetCharmConfig) error {
   922  	m.MethodCall(m, "SetCharm", generation, cfg)
   923  	return m.NextErr()
   924  }
   925  
   926  func (m *mockCharmAPIClient) Get(
   927  	generation model.GenerationVersion, applicationName string,
   928  ) (*params.ApplicationGetResults, error) {
   929  	m.MethodCall(m, "Get", applicationName)
   930  	return &params.ApplicationGetResults{}, m.NextErr()
   931  }
   932  
   933  type mockModelConfigGetter struct {
   934  	ModelConfigGetter
   935  	testing.Stub
   936  }
   937  
   938  func (m *mockModelConfigGetter) ModelGet() (map[string]interface{}, error) {
   939  	m.MethodCall(m, "ModelGet")
   940  	return coretesting.FakeConfig(), m.NextErr()
   941  }
   942  
   943  type mockResourceLister struct {
   944  	ResourceLister
   945  	testing.Stub
   946  }