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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http/httptest"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/juju/cmd/cmdtesting"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/testing"
    22  	jc "github.com/juju/testing/checkers"
    23  	gc "gopkg.in/check.v1"
    24  	"gopkg.in/juju/charm.v6"
    25  	charmresource "gopkg.in/juju/charm.v6/resource"
    26  	"gopkg.in/juju/charmrepo.v3"
    27  	"gopkg.in/juju/charmrepo.v3/csclient"
    28  	"gopkg.in/juju/names.v2"
    29  
    30  	"github.com/juju/juju/api"
    31  	"github.com/juju/juju/caas/kubernetes/provider"
    32  	"github.com/juju/juju/controller"
    33  	"github.com/juju/juju/core/constraints"
    34  	"github.com/juju/juju/resource"
    35  	"github.com/juju/juju/state"
    36  	"github.com/juju/juju/state/multiwatcher"
    37  	"github.com/juju/juju/state/watcher"
    38  	"github.com/juju/juju/storage"
    39  	"github.com/juju/juju/storage/poolmanager"
    40  	dummystorage "github.com/juju/juju/storage/provider/dummy"
    41  	"github.com/juju/juju/testcharms"
    42  	coretesting "github.com/juju/juju/testing"
    43  	"github.com/juju/juju/testing/factory"
    44  )
    45  
    46  // LTS-dependent requires new entry upon new LTS release. There are numerous
    47  // places "xenial" exists in strings throughout this file. If we update the
    48  // target in testing/base.go:SetupSuite we'll need to also update the entries
    49  // herein.
    50  
    51  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNotFoundCharmStore(c *gc.C) {
    52  	err := runDeploy(c, "bundle/no-such")
    53  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:bundle/no-such": bundle not found`)
    54  }
    55  
    56  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidFlags(c *gc.C) {
    57  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
    58  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
    59  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
    60  	err := runDeploy(c, "bundle/wordpress-simple", "--config", "config.yaml")
    61  	c.Assert(err, gc.ErrorMatches, "options provided but not supported when deploying a bundle: --config")
    62  	err = runDeploy(c, "bundle/wordpress-simple", "-n", "2")
    63  	c.Assert(err, gc.ErrorMatches, "options provided but not supported when deploying a bundle: -n")
    64  	err = runDeploy(c, "bundle/wordpress-simple", "--series", "xenial")
    65  	c.Assert(err, gc.ErrorMatches, "options provided but not supported when deploying a bundle: --series")
    66  }
    67  
    68  func (s *BundleDeployCharmStoreSuite) TestDeployBundleSuccess(c *gc.C) {
    69  	_, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
    70  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
    71  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
    72  	err := runDeploy(c, "bundle/wordpress-simple")
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
    75  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
    76  		"mysql":     {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()},
    77  		"wordpress": {charm: "cs:xenial/wordpress-47", config: wpch.Config().DefaultSettings()},
    78  	})
    79  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
    80  	s.assertUnitsCreated(c, map[string]string{
    81  		"mysql/0":     "0",
    82  		"wordpress/0": "1",
    83  	})
    84  }
    85  
    86  func (s *BundleDeployCharmStoreSuite) TestDeployKubernetesBundleSuccess(c *gc.C) {
    87  	// Set up a CAAS model to replace the IAAS one.
    88  	st := s.Factory.MakeCAASModel(c, &factory.ModelParams{
    89  		Name:  "test",
    90  		Owner: names.NewUserTag("admin"),
    91  	})
    92  	s.CleanupSuite.AddCleanup(func(*gc.C) { _ = st.Close() })
    93  
    94  	// Close the state pool before the state object itself.
    95  	c.Assert(s.StatePool.Close(), jc.ErrorIsNil)
    96  	s.StatePool = nil
    97  	err := s.State.Close()
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	s.State = st
   100  
   101  	_, mysqlch := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/mariadb-42", "mariadb", "kubernetes")
   102  	_, wpch := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/gitlab-47", "gitlab", "kubernetes")
   103  	testcharms.UploadBundle(c, s.client, "bundle/kubernetes-simple-1", "kubernetes-simple")
   104  
   105  	settings := state.NewStateSettings(s.State)
   106  	registry := storage.StaticProviderRegistry{
   107  		Providers: map[storage.ProviderType]storage.Provider{
   108  			"kubernetes": &dummystorage.StorageProvider{},
   109  		},
   110  	}
   111  	pm := poolmanager.New(settings, registry)
   112  	_, err = pm.Create("operator-storage", provider.K8s_ProviderType, map[string]interface{}{})
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	err = runDeploy(c, "-m", "admin/test", "bundle/kubernetes-simple")
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	s.assertCharmsUploaded(c, "cs:kubernetes/gitlab-47", "cs:kubernetes/mariadb-42")
   118  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   119  		"mariadb": {charm: "cs:kubernetes/mariadb-42", config: mysqlch.Config().DefaultSettings()},
   120  		"gitlab":  {charm: "cs:kubernetes/gitlab-47", config: wpch.Config().DefaultSettings(), placement: "foo=bar", scale: 1},
   121  	})
   122  	s.assertRelationsEstablished(c, "gitlab:db mariadb:server")
   123  }
   124  
   125  func (s *BundleDeployCharmStoreSuite) TestAddMetricCredentials(c *gc.C) {
   126  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   127  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   128  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-plans-1", "wordpress-with-plans")
   129  
   130  	deploy := NewDeployCommandForTest(
   131  		nil,
   132  		[]DeployStep{&RegisterMeteredCharm{PlanURL: s.server.URL, RegisterPath: "", QueryPath: ""}},
   133  	)
   134  	_, err := cmdtesting.RunCommand(c, deploy, "bundle/wordpress-with-plans")
   135  	c.Assert(err, jc.ErrorIsNil)
   136  
   137  	// The order of calls here does not matter and is, in fact, not guaranteed.
   138  	// All we care about here is that the calls exist.
   139  	s.stub.CheckCallsUnordered(c, []testing.StubCall{{
   140  		FuncName: "DefaultPlan",
   141  		Args:     []interface{}{"cs:wordpress"},
   142  	}, {
   143  		FuncName: "Authorize",
   144  		Args: []interface{}{metricRegistrationPost{
   145  			ModelUUID:       "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   146  			CharmURL:        "cs:wordpress",
   147  			ApplicationName: "wordpress",
   148  			PlanURL:         "thisplan",
   149  			IncreaseBudget:  0,
   150  		}},
   151  	}, {
   152  		FuncName: "Authorize",
   153  		Args: []interface{}{metricRegistrationPost{
   154  			ModelUUID:       "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   155  			CharmURL:        "cs:mysql",
   156  			ApplicationName: "mysql",
   157  			PlanURL:         "test/plan",
   158  			IncreaseBudget:  0,
   159  		}},
   160  	}})
   161  
   162  	mysqlApp, err := s.State.Application("mysql")
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	c.Assert(mysqlApp.MetricCredentials(), jc.DeepEquals, append([]byte(`"aGVsbG8gcmVnaXN0cmF0aW9u"`), 0xA))
   165  
   166  	wordpressApp, err := s.State.Application("wordpress")
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	c.Assert(wordpressApp.MetricCredentials(), jc.DeepEquals, append([]byte(`"aGVsbG8gcmVnaXN0cmF0aW9u"`), 0xA))
   169  }
   170  
   171  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWithTermsSuccess(c *gc.C) {
   172  	_, ch1 := testcharms.UploadCharm(c, s.client, "xenial/terms1-17", "terms1")
   173  	_, ch2 := testcharms.UploadCharm(c, s.client, "xenial/terms2-42", "terms2")
   174  	testcharms.UploadBundle(c, s.client, "bundle/terms-simple-1", "terms-simple")
   175  	err := runDeploy(c, "bundle/terms-simple")
   176  	c.Assert(err, jc.ErrorIsNil)
   177  	s.assertCharmsUploaded(c, "cs:xenial/terms1-17", "cs:xenial/terms2-42")
   178  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   179  		"terms1": {charm: "cs:xenial/terms1-17", config: ch1.Config().DefaultSettings()},
   180  		"terms2": {charm: "cs:xenial/terms2-42", config: ch2.Config().DefaultSettings()},
   181  	})
   182  	s.assertUnitsCreated(c, map[string]string{
   183  		"terms1/0": "0",
   184  		"terms2/0": "1",
   185  	})
   186  	c.Assert(s.termsString, gc.Not(gc.Equals), "")
   187  }
   188  
   189  func (s *BundleDeployCharmStoreSuite) TestDeployBundleStorage(c *gc.C) {
   190  	_, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql-storage")
   191  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   192  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-mysql-storage-1", "wordpress-with-mysql-storage")
   193  	err := runDeploy(
   194  		c, "bundle/wordpress-with-mysql-storage",
   195  		"--storage", "mysql:logs=tmpfs,10G", // override logs
   196  	)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   199  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   200  		"mysql": {
   201  			charm:  "cs:xenial/mysql-42",
   202  			config: mysqlch.Config().DefaultSettings(),
   203  			storage: map[string]state.StorageConstraints{
   204  				"data": {Pool: "rootfs", Size: 50 * 1024, Count: 1},
   205  				"logs": {Pool: "tmpfs", Size: 10 * 1024, Count: 1},
   206  			},
   207  		},
   208  		"wordpress": {charm: "cs:xenial/wordpress-47", config: wpch.Config().DefaultSettings()},
   209  	})
   210  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   211  	s.assertUnitsCreated(c, map[string]string{
   212  		"mysql/0":     "0",
   213  		"wordpress/0": "1",
   214  	})
   215  }
   216  
   217  type CAASModelDeployCharmStoreSuite struct {
   218  	CAASDeploySuiteBase
   219  }
   220  
   221  var _ = gc.Suite(&CAASModelDeployCharmStoreSuite{})
   222  
   223  func (s *CAASModelDeployCharmStoreSuite) TestDeployBundleDevices(c *gc.C) {
   224  	c.Skip("Test disabled until flakiness is fixed - see bug lp:1781250")
   225  
   226  	m, err := s.State.Model()
   227  	c.Assert(err, jc.ErrorIsNil)
   228  
   229  	_, minerCharm := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/bitcoin-miner-1", "bitcoin-miner", "kubernetes")
   230  	_, dashboardCharm := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/dashboard4miner-3", "dashboard4miner", "kubernetes")
   231  
   232  	testcharms.UploadBundle(c, s.client, "bundle/bitcoinminer-with-dashboard-1", "bitcoinminer-with-dashboard")
   233  	err = runDeploy(
   234  		c, "bundle/bitcoinminer-with-dashboard",
   235  		"-m", m.Name(),
   236  		"--device", "miner:bitcoinminer=10,nvidia.com/gpu", // override bitcoinminer
   237  	)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	s.assertCharmsUploaded(c, "cs:kubernetes/dashboard4miner-3", "cs:kubernetes/bitcoin-miner-1")
   240  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   241  		"miner": {
   242  			charm:  "cs:kubernetes/bitcoin-miner-1",
   243  			config: minerCharm.Config().DefaultSettings(),
   244  			devices: map[string]state.DeviceConstraints{
   245  				"bitcoinminer": {Type: "nvidia.com/gpu", Count: 10, Attributes: map[string]string{}},
   246  			},
   247  		},
   248  		"dashboard": {charm: "cs:kubernetes/dashboard4miner-3", config: dashboardCharm.Config().DefaultSettings()},
   249  	})
   250  	s.assertRelationsEstablished(c, "dashboard:miner miner:miner")
   251  	s.assertUnitsCreated(c, map[string]string{
   252  		"miner/0":     "",
   253  		"dashboard/0": "",
   254  	})
   255  }
   256  
   257  func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSpaceMissing(c *gc.C) {
   258  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   259  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings")
   260  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings")
   261  	stdOut, stdErr, err := runDeployWithOutput(c, "bundle/wordpress-with-endpoint-bindings")
   262  	c.Assert(err, gc.ErrorMatches, ""+
   263  		"cannot deploy bundle: cannot deploy application \"mysql\": "+
   264  		"cannot add application \"mysql\": unknown space \"db\" not valid")
   265  	c.Assert(stdErr, gc.Equals, ""+
   266  		`Located bundle "cs:bundle/wordpress-with-endpoint-bindings-1"`+"\n"+
   267  		"Resolving charm: mysql\n"+
   268  		"Resolving charm: wordpress-extra-bindings")
   269  	c.Assert(stdOut, gc.Equals, ""+
   270  		"Executing changes:\n"+
   271  		"- upload charm cs:xenial/mysql-42 for series xenial\n"+
   272  		"- deploy application mysql on xenial using cs:xenial/mysql-42")
   273  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42")
   274  	s.assertApplicationsDeployed(c, map[string]applicationInfo{})
   275  	s.assertUnitsCreated(c, map[string]string{})
   276  }
   277  
   278  func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSuccess(c *gc.C) {
   279  	_, err := s.State.AddSpace("db", "", nil, false)
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	_, err = s.State.AddSpace("public", "", nil, false)
   282  	c.Assert(err, jc.ErrorIsNil)
   283  
   284  	_, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   285  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings")
   286  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings")
   287  	err = runDeploy(c, "bundle/wordpress-with-endpoint-bindings")
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-extra-bindings-47")
   290  
   291  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   292  		"mysql":                    {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()},
   293  		"wordpress-extra-bindings": {charm: "cs:xenial/wordpress-extra-bindings-47", config: wpch.Config().DefaultSettings()},
   294  	})
   295  	s.assertDeployedApplicationBindings(c, map[string]applicationInfo{
   296  		"mysql": {
   297  			endpointBindings: map[string]string{"server": "db", "server-admin": "", "metrics-client": ""},
   298  		},
   299  		"wordpress-extra-bindings": {
   300  			endpointBindings: map[string]string{
   301  				"cache":           "",
   302  				"url":             "public",
   303  				"logging-dir":     "",
   304  				"monitoring-port": "",
   305  				"db":              "db",
   306  				"cluster":         "",
   307  				"db-client":       "db",
   308  				"admin-api":       "public",
   309  				"foo-bar":         "",
   310  			},
   311  		},
   312  	})
   313  	s.assertRelationsEstablished(c, "wordpress-extra-bindings:cluster", "wordpress-extra-bindings:db mysql:server")
   314  	s.assertUnitsCreated(c, map[string]string{
   315  		"mysql/0":                    "0",
   316  		"wordpress-extra-bindings/0": "1",
   317  	})
   318  }
   319  
   320  func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwice(c *gc.C) {
   321  	_, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   322  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   323  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   324  	stdOut, stdErr, err := runDeployWithOutput(c, "bundle/wordpress-simple")
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	c.Check(stdOut, gc.Equals, ""+
   327  		"Executing changes:\n"+
   328  		"- upload charm cs:xenial/mysql-42 for series xenial\n"+
   329  		"- deploy application mysql on xenial using cs:xenial/mysql-42\n"+
   330  		"- set annotations for mysql\n"+
   331  		"- upload charm cs:xenial/wordpress-47 for series xenial\n"+
   332  		"- deploy application wordpress on xenial using cs:xenial/wordpress-47\n"+
   333  		"- set annotations for wordpress\n"+
   334  		"- add relation wordpress:db - mysql:server\n"+
   335  		"- add unit mysql/0 to new machine 0\n"+
   336  		"- add unit wordpress/0 to new machine 1",
   337  	)
   338  	stdOut, stdErr, err = runDeployWithOutput(c, "bundle/wordpress-simple")
   339  	c.Assert(err, jc.ErrorIsNil)
   340  	// Nothing to do...
   341  	c.Check(stdOut, gc.Equals, "")
   342  	c.Check(stdErr, gc.Equals, ""+
   343  		"Located bundle \"cs:bundle/wordpress-simple-1\"\n"+
   344  		"Resolving charm: mysql\n"+
   345  		"Resolving charm: wordpress\n"+
   346  		"No changes to apply.",
   347  	)
   348  
   349  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   350  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   351  		"mysql":     {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()},
   352  		"wordpress": {charm: "cs:xenial/wordpress-47", config: wpch.Config().DefaultSettings()},
   353  	})
   354  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   355  	s.assertUnitsCreated(c, map[string]string{
   356  		"mysql/0":     "0",
   357  		"wordpress/0": "1",
   358  	})
   359  }
   360  
   361  func (s *BundleDeployCharmStoreSuite) TestDryRunTwice(c *gc.C) {
   362  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   363  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   364  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   365  	stdOut, _, err := runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run")
   366  	c.Assert(err, jc.ErrorIsNil)
   367  	expected := "" +
   368  		"Changes to deploy bundle:\n" +
   369  		"- upload charm cs:xenial/mysql-42 for series xenial\n" +
   370  		"- deploy application mysql on xenial using cs:xenial/mysql-42\n" +
   371  		"- set annotations for mysql\n" +
   372  		"- upload charm cs:xenial/wordpress-47 for series xenial\n" +
   373  		"- deploy application wordpress on xenial using cs:xenial/wordpress-47\n" +
   374  		"- set annotations for wordpress\n" +
   375  		"- add relation wordpress:db - mysql:server\n" +
   376  		"- add unit mysql/0 to new machine 0\n" +
   377  		"- add unit wordpress/0 to new machine 1"
   378  
   379  	c.Check(stdOut, gc.Equals, expected)
   380  	stdOut, _, err = runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run")
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Check(stdOut, gc.Equals, expected)
   383  
   384  	s.assertCharmsUploaded(c /* none */)
   385  	s.assertApplicationsDeployed(c, map[string]applicationInfo{})
   386  	s.assertRelationsEstablished(c /* none */)
   387  	s.assertUnitsCreated(c, map[string]string{})
   388  }
   389  
   390  func (s *BundleDeployCharmStoreSuite) TestDryRunExistingModel(c *gc.C) {
   391  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   392  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   393  	testcharms.UploadCharm(c, s.client, "trusty/multi-series-subordinate-13", "multi-series-subordinate")
   394  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   395  	// Start with a mysql that already has the right charm.
   396  	ch := s.Factory.MakeCharm(c, &factory.CharmParams{
   397  		Name: "mysql", Series: "xenial", Revision: "42"})
   398  	mysql := s.Factory.MakeApplication(c, &factory.ApplicationParams{
   399  		Name: "mysql", Charm: ch})
   400  	s.Factory.MakeUnit(c, &factory.UnitParams{Application: mysql})
   401  	// Also add a subordinate that isn't attached to anything.
   402  	sub := s.Factory.MakeCharm(c, &factory.CharmParams{
   403  		Name: "multi-series-subordinate", Series: "trusty", Revision: "13"})
   404  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
   405  		Name: "sub", Charm: sub})
   406  
   407  	stdOut, _, err := runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run")
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	expected := "" +
   410  		"Changes to deploy bundle:\n" +
   411  		"- set annotations for mysql\n" +
   412  		"- upload charm cs:xenial/wordpress-47 for series xenial\n" +
   413  		"- deploy application wordpress on xenial using cs:xenial/wordpress-47\n" +
   414  		"- set annotations for wordpress\n" +
   415  		"- add relation wordpress:db - mysql:server\n" +
   416  		"- add unit wordpress/0 to new machine 1"
   417  
   418  	c.Check(stdOut, gc.Equals, expected)
   419  	stdOut, _, err = runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run")
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	c.Check(stdOut, gc.Equals, expected)
   422  }
   423  
   424  func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharm(c *gc.C) {
   425  	_, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   426  	url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   427  	s.changeReadPerm(c, url, clientUserName)
   428  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   429  	err := runDeploy(c, "bundle/wordpress-simple")
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   432  	ch, err := s.State.Charm(charm.MustParseURL("cs:xenial/wordpress-47"))
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   435  		"mysql":     {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()},
   436  		"wordpress": {charm: "cs:xenial/wordpress-47", config: ch.Config().DefaultSettings()},
   437  	})
   438  }
   439  
   440  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalPath(c *gc.C) {
   441  	dir := c.MkDir()
   442  	testcharms.Repo.ClonedDir(dir, "dummy")
   443  	path := filepath.Join(dir, "mybundle")
   444  	data := `
   445          series: xenial
   446          applications:
   447              dummy:
   448                  charm: ./dummy
   449                  series: xenial
   450                  num_units: 1
   451      `
   452  	err := ioutil.WriteFile(path, []byte(data), 0644)
   453  	c.Assert(err, jc.ErrorIsNil)
   454  	err = runDeploy(c, path)
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	s.assertCharmsUploaded(c, "local:xenial/dummy-1")
   457  	ch, err := s.State.Charm(charm.MustParseURL("local:xenial/dummy-1"))
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   460  		"dummy": {charm: "local:xenial/dummy-1", config: ch.Config().DefaultSettings()},
   461  	})
   462  }
   463  
   464  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalResources(c *gc.C) {
   465  	data := `
   466          series: quantal
   467          applications:
   468              "dummy-resource":
   469                  charm: ./dummy-resource
   470                  series: quantal
   471                  num_units: 1
   472                  resources:
   473                    dummy: ./dummy-resource.zip
   474      `
   475  	dir := s.makeBundleDir(c, data)
   476  	testcharms.Repo.ClonedDir(dir, "dummy-resource")
   477  	c.Assert(
   478  		ioutil.WriteFile(filepath.Join(dir, "dummy-resource.zip"), []byte("zip file"), 0644),
   479  		jc.ErrorIsNil)
   480  	err := runDeploy(c, dir)
   481  	c.Assert(err, jc.ErrorIsNil)
   482  	s.assertCharmsUploaded(c, "local:quantal/dummy-resource-0")
   483  	ch, err := s.State.Charm(charm.MustParseURL("local:quantal/dummy-resource-0"))
   484  	c.Assert(err, jc.ErrorIsNil)
   485  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   486  		"dummy-resource": {charm: "local:quantal/dummy-resource-0", config: ch.Config().DefaultSettings()},
   487  	})
   488  }
   489  
   490  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNoSeriesInCharmURL(c *gc.C) {
   491  	testcharms.UploadCharmMultiSeries(c, s.client, "~who/multi-series", "multi-series")
   492  	dir := c.MkDir()
   493  	testcharms.Repo.ClonedDir(dir, "dummy")
   494  	path := filepath.Join(dir, "mybundle")
   495  	data := `
   496          series: trusty
   497          applications:
   498              dummy:
   499                  charm: cs:~who/multi-series
   500      `
   501  	err := ioutil.WriteFile(path, []byte(data), 0644)
   502  	c.Assert(err, jc.ErrorIsNil)
   503  	err = runDeploy(c, path)
   504  	c.Assert(err, jc.ErrorIsNil)
   505  	s.assertCharmsUploaded(c, "cs:~who/multi-series-0")
   506  	ch, err := s.State.Charm(charm.MustParseURL("~who/multi-series-0"))
   507  	c.Assert(err, jc.ErrorIsNil)
   508  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   509  		"dummy": {charm: "cs:~who/multi-series-0", config: ch.Config().DefaultSettings()},
   510  	})
   511  }
   512  
   513  func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharmUnauthorized(c *gc.C) {
   514  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   515  	url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   516  	s.changeReadPerm(c, url, "who")
   517  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   518  	err := runDeploy(c, "bundle/wordpress-simple")
   519  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: .*: access denied for user "client-username"`)
   520  }
   521  
   522  func (s *BundleDeployCharmStoreSuite) TestDeployBundleResources(c *gc.C) {
   523  	testcharms.UploadCharm(c, s.Client(), "trusty/starsay-42", "starsay")
   524  	bundleMeta := `
   525          applications:
   526              starsay:
   527                  charm: cs:starsay
   528                  num_units: 1
   529                  resources:
   530                      store-resource: 0
   531                      install-resource: 0
   532                      upload-resource: 0
   533      `
   534  	stdOut, stdErr, err := s.DeployBundleYAMLWithOutput(c, bundleMeta)
   535  	c.Assert(err, jc.ErrorIsNil)
   536  
   537  	c.Check(stdOut, gc.Equals, ""+
   538  		"Executing changes:\n"+
   539  		"- upload charm cs:trusty/starsay-42 for series trusty\n"+
   540  		"- deploy application starsay on trusty using cs:trusty/starsay-42\n"+
   541  		"- add unit starsay/0 to new machine 0",
   542  	)
   543  	// Info messages go to stdErr.
   544  	c.Check(stdErr, gc.Equals, ""+
   545  		"Resolving charm: cs:starsay\n"+
   546  		"  added resource install-resource\n"+
   547  		"  added resource store-resource\n"+
   548  		"  added resource upload-resource\n"+
   549  		"Deploy of bundle completed.",
   550  	)
   551  
   552  	resourceHash := func(content string) charmresource.Fingerprint {
   553  		fp, err := charmresource.GenerateFingerprint(strings.NewReader(content))
   554  		c.Assert(err, jc.ErrorIsNil)
   555  		return fp
   556  	}
   557  
   558  	s.checkResources(c, "starsay", []resource.Resource{{
   559  		Resource: charmresource.Resource{
   560  			Meta: charmresource.Meta{
   561  				Name:        "install-resource",
   562  				Type:        charmresource.TypeFile,
   563  				Path:        "gotta-have-it.txt",
   564  				Description: "get things started",
   565  			},
   566  			Origin:      charmresource.OriginStore,
   567  			Revision:    0,
   568  			Fingerprint: resourceHash("install-resource content"),
   569  			Size:        int64(len("install-resource content")),
   570  		},
   571  		ID:            "starsay/install-resource",
   572  		ApplicationID: "starsay",
   573  	}, {
   574  		Resource: charmresource.Resource{
   575  			Meta: charmresource.Meta{
   576  				Name:        "store-resource",
   577  				Type:        charmresource.TypeFile,
   578  				Path:        "filename.tgz",
   579  				Description: "One line that is useful when operators need to push it.",
   580  			},
   581  			Origin:      charmresource.OriginStore,
   582  			Fingerprint: resourceHash("store-resource content"),
   583  			Size:        int64(len("store-resource content")),
   584  			Revision:    0,
   585  		},
   586  		ID:            "starsay/store-resource",
   587  		ApplicationID: "starsay",
   588  	}, {
   589  		Resource: charmresource.Resource{
   590  			Meta: charmresource.Meta{
   591  				Name:        "upload-resource",
   592  				Type:        charmresource.TypeFile,
   593  				Path:        "somename.xml",
   594  				Description: "Who uses xml anymore?",
   595  			},
   596  			Origin:      charmresource.OriginStore,
   597  			Fingerprint: resourceHash("upload-resource content"),
   598  			Size:        int64(len("upload-resource content")),
   599  			Revision:    0,
   600  		},
   601  		ID:            "starsay/upload-resource",
   602  		ApplicationID: "starsay",
   603  	}})
   604  }
   605  
   606  func (s *BundleDeployCharmStoreSuite) checkResources(c *gc.C, serviceapplication string, expected []resource.Resource) {
   607  	_, err := s.State.Application("starsay")
   608  	c.Check(err, jc.ErrorIsNil)
   609  	st, err := s.State.Resources()
   610  	c.Assert(err, jc.ErrorIsNil)
   611  	svcResources, err := st.ListResources("starsay")
   612  	c.Assert(err, jc.ErrorIsNil)
   613  	resources := svcResources.Resources
   614  	resource.Sort(resources)
   615  	c.Assert(resources, jc.DeepEquals, expected)
   616  }
   617  
   618  type BundleDeployCharmStoreSuite struct {
   619  	charmStoreSuite
   620  
   621  	stub   *testing.Stub
   622  	server *httptest.Server
   623  }
   624  
   625  var _ = gc.Suite(&BundleDeployCharmStoreSuite{})
   626  
   627  func (s *BundleDeployCharmStoreSuite) SetUpSuite(c *gc.C) {
   628  	s.charmStoreSuite.SetUpSuite(c)
   629  	s.PatchValue(&watcher.Period, 10*time.Millisecond)
   630  }
   631  
   632  func (s *BundleDeployCharmStoreSuite) SetUpTest(c *gc.C) {
   633  	s.stub = &testing.Stub{}
   634  	handler := &testMetricsRegistrationHandler{Stub: s.stub}
   635  	s.server = httptest.NewServer(handler)
   636  	// Set metering URL config so the config is set during bootstrap
   637  	if s.ControllerConfigAttrs == nil {
   638  		s.ControllerConfigAttrs = make(map[string]interface{})
   639  	}
   640  	s.ControllerConfigAttrs[controller.MeteringURL] = s.server.URL
   641  
   642  	s.charmStoreSuite.SetUpTest(c)
   643  	logger.SetLogLevel(loggo.TRACE)
   644  }
   645  
   646  func (s *BundleDeployCharmStoreSuite) TearDownTest(c *gc.C) {
   647  	if s.server != nil {
   648  		s.server.Close()
   649  	}
   650  	s.charmStoreSuite.TearDownTest(c)
   651  }
   652  
   653  func (s *BundleDeployCharmStoreSuite) Client() *csclient.Client {
   654  	return s.client
   655  }
   656  
   657  // DeployBundleYAML uses the given bundle content to create a bundle in the
   658  // local repository and then deploy it. It returns the bundle deployment output
   659  // and error.
   660  func (s *BundleDeployCharmStoreSuite) DeployBundleYAML(c *gc.C, content string, extraArgs ...string) error {
   661  	_, _, err := s.DeployBundleYAMLWithOutput(c, content, extraArgs...)
   662  	return err
   663  }
   664  
   665  func (s *BundleDeployCharmStoreSuite) DeployBundleYAMLWithOutput(c *gc.C, content string, extraArgs ...string) (string, string, error) {
   666  	bundlePath := s.makeBundleDir(c, content)
   667  	args := append([]string{bundlePath}, extraArgs...)
   668  	return runDeployWithOutput(c, args...)
   669  }
   670  
   671  func (s *BundleDeployCharmStoreSuite) makeBundleDir(c *gc.C, content string) string {
   672  	bundlePath := filepath.Join(c.MkDir(), "example")
   673  	c.Assert(os.Mkdir(bundlePath, 0777), jc.ErrorIsNil)
   674  	err := ioutil.WriteFile(filepath.Join(bundlePath, "bundle.yaml"), []byte(content), 0644)
   675  	c.Assert(err, jc.ErrorIsNil)
   676  	err = ioutil.WriteFile(filepath.Join(bundlePath, "README.md"), []byte("README"), 0644)
   677  	c.Assert(err, jc.ErrorIsNil)
   678  	return bundlePath
   679  }
   680  
   681  var deployBundleErrorsTests = []struct {
   682  	about   string
   683  	content string
   684  	err     string
   685  }{{
   686  	about: "local charm not found",
   687  	content: `
   688          applications:
   689              mysql:
   690                  charm: ./mysql
   691                  num_units: 1
   692      `,
   693  	err: `the provided bundle has the following errors:
   694  charm path in application "mysql" does not exist: .*mysql`,
   695  }, {
   696  	about: "charm store charm not found",
   697  	content: `
   698          applications:
   699              rails:
   700                  charm: xenial/rails-42
   701                  num_units: 1
   702      `,
   703  	err: `cannot resolve URL "xenial/rails-42": cannot resolve URL "cs:xenial/rails-42": charm not found`,
   704  }, {
   705  	about:   "invalid bundle content",
   706  	content: "!",
   707  	err:     `(?s)cannot unmarshal bundle data: yaml: .*`,
   708  }, {
   709  	about: "invalid bundle data",
   710  	content: `
   711          applications:
   712              mysql:
   713                  charm: mysql
   714                  num_units: -1
   715      `,
   716  	err: `the provided bundle has the following errors:
   717  negative number of units specified on application "mysql"`,
   718  }, {
   719  	about: "invalid constraints",
   720  	content: `
   721          applications:
   722              mysql:
   723                  charm: mysql
   724                  num_units: 1
   725                  constraints: bad-wolf
   726      `,
   727  	err: `the provided bundle has the following errors:
   728  invalid constraints "bad-wolf" in application "mysql": malformed constraint "bad-wolf"`,
   729  }, {
   730  	about: "multiple bundle verification errors",
   731  	content: `
   732          applications:
   733              mysql:
   734                  charm: mysql
   735                  num_units: -1
   736                  constraints: bad-wolf
   737      `,
   738  	err: `the provided bundle has the following errors:
   739  invalid constraints "bad-wolf" in application "mysql": malformed constraint "bad-wolf"
   740  negative number of units specified on application "mysql"`,
   741  }, {
   742  	about: "bundle inception",
   743  	content: `
   744          applications:
   745              example:
   746                  charm: local:wordpress
   747                  num_units: 1
   748      `,
   749  	err: `cannot resolve URL "local:wordpress": unknown schema for charm URL "local:wordpress"`,
   750  }}
   751  
   752  func (s *BundleDeployCharmStoreSuite) TestDeployBundleErrors(c *gc.C) {
   753  	for i, test := range deployBundleErrorsTests {
   754  		c.Logf("test %d: %s", i, test.about)
   755  		err := s.DeployBundleYAML(c, test.content)
   756  		pass := c.Check(err, gc.ErrorMatches, "cannot deploy bundle: "+test.err)
   757  		if !pass {
   758  			c.Logf("error: \n%s\n", errors.ErrorStack(err))
   759  		}
   760  	}
   761  }
   762  
   763  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidOptions(c *gc.C) {
   764  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   765  	err := s.DeployBundleYAML(c, `
   766          applications:
   767              wp:
   768                  charm: xenial/wordpress-42
   769                  num_units: 1
   770                  options:
   771                      blog-title: 42
   772      `)
   773  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": option "blog-title" expected string, got 42`)
   774  }
   775  
   776  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidMachineContainerType(c *gc.C) {
   777  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   778  	err := s.DeployBundleYAML(c, `
   779          applications:
   780              wp:
   781                  charm: xenial/wordpress
   782                  num_units: 1
   783                  to: ["bad:1"]
   784          machines:
   785              1:
   786      `)
   787  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot create machine for holding wp unit: invalid container type "bad"`)
   788  }
   789  
   790  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidSeries(c *gc.C) {
   791  	testcharms.UploadCharm(c, s.client, "vivid/django-0", "dummy")
   792  	err := s.DeployBundleYAML(c, `
   793          applications:
   794              django:
   795                  charm: vivid/django
   796                  num_units: 1
   797                  to:
   798                      - 1
   799          machines:
   800              1:
   801                  series: xenial
   802      `)
   803  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot add unit for application "django": acquiring machine to host unit "django/0": cannot assign unit "django/0" to machine 0: series does not match`)
   804  }
   805  
   806  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidBinding(c *gc.C) {
   807  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   808  	err := s.DeployBundleYAML(c, `
   809          applications:
   810              wp:
   811                  charm: xenial/wordpress-42
   812                  num_units: 1
   813                  bindings:
   814                    noturl: public
   815      `)
   816  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": invalid binding\(s\) supplied "noturl", valid binding names are "admin-api",.* "url"`)
   817  }
   818  
   819  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidSpace(c *gc.C) {
   820  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   821  	err := s.DeployBundleYAML(c, `
   822          applications:
   823              wp:
   824                  charm: xenial/wordpress-42
   825                  num_units: 1
   826                  bindings:
   827                    url: public
   828      `)
   829  	// TODO(jam): 2017-02-05 double repeating "cannot deploy application" and "cannot add application" is a bit ugly
   830  	// https://pad.lv/1661937
   831  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": cannot add application "wp": unknown space "public" not valid`)
   832  }
   833  
   834  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWatcherTimeout(c *gc.C) {
   835  	// Inject an "AllWatcher" that never delivers a result.
   836  	ch := make(chan struct{})
   837  	defer close(ch)
   838  	watcher := mockAllWatcher{
   839  		next: func() []multiwatcher.Delta {
   840  			<-ch
   841  			return nil
   842  		},
   843  	}
   844  	s.PatchValue(&watchAll, func(*api.Client) (allWatcher, error) {
   845  		return watcher, nil
   846  	})
   847  
   848  	testcharms.UploadCharm(c, s.client, "xenial/django-0", "dummy")
   849  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   850  	s.PatchValue(&updateUnitStatusPeriod, 0*time.Second)
   851  	err := s.DeployBundleYAML(c, `
   852          applications:
   853              django:
   854                  charm: django
   855                  num_units: 1
   856              wordpress:
   857                  charm: wordpress
   858                  num_units: 1
   859                  to: [django]
   860      `)
   861  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot retrieve placement for "wordpress" unit: cannot resolve machine: timeout while trying to get new changes from the watcher`)
   862  }
   863  
   864  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeployment(c *gc.C) {
   865  	charmsPath := c.MkDir()
   866  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   867  	wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress")
   868  	err := s.DeployBundleYAML(c, fmt.Sprintf(`
   869          series: xenial
   870          applications:
   871              wordpress:
   872                  charm: %s
   873                  num_units: 1
   874              mysql:
   875                  charm: %s
   876                  num_units: 2
   877          relations:
   878              - ["wordpress:db", "mysql:server"]
   879      `, wordpressPath, mysqlPath))
   880  	c.Assert(err, jc.ErrorIsNil)
   881  	s.assertCharmsUploaded(c, "local:xenial/mysql-1", "local:xenial/wordpress-3")
   882  	mysqlch, err := s.State.Charm(charm.MustParseURL("local:xenial/mysql-1"))
   883  	c.Assert(err, jc.ErrorIsNil)
   884  	wpch, err := s.State.Charm(charm.MustParseURL("local:xenial/wordpress-3"))
   885  	c.Assert(err, jc.ErrorIsNil)
   886  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   887  		"mysql":     {charm: "local:xenial/mysql-1", config: mysqlch.Config().DefaultSettings()},
   888  		"wordpress": {charm: "local:xenial/wordpress-3", config: wpch.Config().DefaultSettings()},
   889  	})
   890  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   891  	s.assertUnitsCreated(c, map[string]string{
   892  		"mysql/0":     "0",
   893  		"mysql/1":     "1",
   894  		"wordpress/0": "2",
   895  	})
   896  }
   897  
   898  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentBadConfig(c *gc.C) {
   899  	charmsPath := c.MkDir()
   900  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   901  	wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress")
   902  	err := s.DeployBundleYAML(c, fmt.Sprintf(`
   903          series: xenial
   904          applications:
   905              wordpress:
   906                  charm: %s
   907                  num_units: 1
   908              mysql:
   909                  charm: %s
   910                  num_units: 2
   911          relations:
   912              - ["wordpress:db", "mysql:server"]
   913      `, wordpressPath, mysqlPath),
   914  		"--overlay", "missing-file")
   915  	c.Assert(err, gc.ErrorMatches, "cannot deploy bundle: unable to process overlays: unable to read bundle overlay file .*")
   916  }
   917  
   918  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentLXDProfile(c *gc.C) {
   919  	charmsPath := c.MkDir()
   920  	lxdProfilePath := testcharms.Repo.ClonedDirPath(charmsPath, "lxd-profile")
   921  	err := s.DeployBundleYAML(c, fmt.Sprintf(`
   922          series: bionic
   923          services:
   924              lxd-profile:
   925                  charm: %s
   926                  num_units: 1
   927      `, lxdProfilePath))
   928  	c.Assert(err, jc.ErrorIsNil)
   929  	s.assertCharmsUploaded(c, "local:bionic/lxd-profile-0")
   930  	lxdProfile, err := s.State.Charm(charm.MustParseURL("local:bionic/lxd-profile-0"))
   931  	c.Assert(err, jc.ErrorIsNil)
   932  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
   933  		"lxd-profile": {charm: "local:bionic/lxd-profile-0", config: lxdProfile.Config().DefaultSettings()},
   934  	})
   935  	s.assertUnitsCreated(c, map[string]string{
   936  		"lxd-profile/0": "0",
   937  	})
   938  }
   939  
   940  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentBadLXDProfile(c *gc.C) {
   941  	charmsPath := c.MkDir()
   942  	lxdProfilePath := testcharms.Repo.ClonedDirPath(charmsPath, "lxd-profile-fail")
   943  	err := s.DeployBundleYAML(c, fmt.Sprintf(`
   944          series: bionic
   945          services:
   946              lxd-profile-fail:
   947                  charm: %s
   948                  num_units: 1
   949      `, lxdProfilePath))
   950  	c.Assert(err, gc.ErrorMatches, "cannot deploy bundle: cannot deploy local charm at .*: invalid lxd-profile.yaml: contains device type \"unix-disk\"")
   951  }
   952  
   953  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentWithBundleOverlay(c *gc.C) {
   954  	configDir := c.MkDir()
   955  	configFile := filepath.Join(configDir, "config.yaml")
   956  	c.Assert(
   957  		ioutil.WriteFile(
   958  			configFile, []byte(`
   959                  applications:
   960                      wordpress:
   961                          options:
   962                              blog-title: include-file://title
   963              `), 0644),
   964  		jc.ErrorIsNil)
   965  	c.Assert(
   966  		ioutil.WriteFile(
   967  			filepath.Join(configDir, "title"), []byte("magic bundle config"), 0644),
   968  		jc.ErrorIsNil)
   969  
   970  	charmsPath := c.MkDir()
   971  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   972  	wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress")
   973  	err := s.DeployBundleYAML(c, fmt.Sprintf(`
   974          series: xenial
   975          applications:
   976              wordpress:
   977                  charm: %s
   978                  num_units: 1
   979              mysql:
   980                  charm: %s
   981                  num_units: 2
   982          relations:
   983              - ["wordpress:db", "mysql:server"]
   984      `, wordpressPath, mysqlPath),
   985  		"--overlay", configFile)
   986  
   987  	c.Assert(err, jc.ErrorIsNil)
   988  	// Now check the blog-title of the wordpress.	le")
   989  	wordpress, err := s.State.Application("wordpress")
   990  	c.Assert(err, jc.ErrorIsNil)
   991  	settings, err := wordpress.CharmConfig()
   992  	c.Assert(err, jc.ErrorIsNil)
   993  	c.Assert(settings["blog-title"], gc.Equals, "magic bundle config")
   994  }
   995  
   996  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalAndCharmStoreCharms(c *gc.C) {
   997  	charmsPath := c.MkDir()
   998  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   999  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
  1000  	err := s.DeployBundleYAML(c, fmt.Sprintf(`
  1001          series: xenial
  1002          applications:
  1003              wordpress:
  1004                  charm: xenial/wordpress-42
  1005                  series: xenial
  1006                  num_units: 1
  1007              mysql:
  1008                  charm: %s
  1009                  num_units: 1
  1010          relations:
  1011              - ["wordpress:db", "mysql:server"]
  1012      `, mysqlPath))
  1013  	c.Assert(err, jc.ErrorIsNil)
  1014  	s.assertCharmsUploaded(c, "local:xenial/mysql-1", "cs:xenial/wordpress-42")
  1015  	mysqlch, err := s.State.Charm(charm.MustParseURL("local:xenial/mysql-1"))
  1016  	c.Assert(err, jc.ErrorIsNil)
  1017  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
  1018  		"mysql":     {charm: "local:xenial/mysql-1", config: mysqlch.Config().DefaultSettings()},
  1019  		"wordpress": {charm: "cs:xenial/wordpress-42", config: wpch.Config().DefaultSettings()},
  1020  	})
  1021  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
  1022  	s.assertUnitsCreated(c, map[string]string{
  1023  		"mysql/0":     "0",
  1024  		"wordpress/0": "1",
  1025  	})
  1026  }
  1027  
  1028  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationOptions(c *gc.C) {
  1029  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
  1030  	_, dch := testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy")
  1031  	err := s.DeployBundleYAML(c, `
  1032          applications:
  1033              wordpress:
  1034                  charm: wordpress
  1035                  num_units: 1
  1036                  options:
  1037                      blog-title: these are the voyages
  1038              customized:
  1039                  charm: precise/dummy-0
  1040                  num_units: 1
  1041                  options:
  1042                      username: who
  1043                      skill-level: 47
  1044      `)
  1045  	c.Assert(err, jc.ErrorIsNil)
  1046  	s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42")
  1047  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
  1048  		"customized": {
  1049  			charm:  "cs:precise/dummy-0",
  1050  			config: s.combinedSettings(dch, charm.Settings{"username": "who", "skill-level": int64(47)}),
  1051  		},
  1052  		"wordpress": {
  1053  			charm:  "cs:xenial/wordpress-42",
  1054  			config: s.combinedSettings(wpch, charm.Settings{"blog-title": "these are the voyages"}),
  1055  		},
  1056  	})
  1057  	s.assertUnitsCreated(c, map[string]string{
  1058  		"wordpress/0":  "1",
  1059  		"customized/0": "0",
  1060  	})
  1061  }
  1062  
  1063  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationConstraints(c *gc.C) {
  1064  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
  1065  	_, dch := testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy")
  1066  	err := s.DeployBundleYAML(c, `
  1067          applications:
  1068              wordpress:
  1069                  charm: wordpress
  1070                  constraints: mem=4G cores=2
  1071              customized:
  1072                  charm: precise/dummy-0
  1073                  num_units: 1
  1074                  constraints: arch=i386
  1075      `)
  1076  	c.Assert(err, jc.ErrorIsNil)
  1077  	s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42")
  1078  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
  1079  		"customized": {
  1080  			charm:       "cs:precise/dummy-0",
  1081  			constraints: constraints.MustParse("arch=i386"),
  1082  			config:      dch.Config().DefaultSettings(),
  1083  		},
  1084  		"wordpress": {
  1085  			charm:       "cs:xenial/wordpress-42",
  1086  			constraints: constraints.MustParse("mem=4G cores=2"),
  1087  			config:      wpch.Config().DefaultSettings(),
  1088  		},
  1089  	})
  1090  	s.assertUnitsCreated(c, map[string]string{
  1091  		"customized/0": "0",
  1092  	})
  1093  }
  1094  
  1095  func (s *BundleDeployCharmStoreSuite) TestDeployBundleSetAnnotations(c *gc.C) {
  1096  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
  1097  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
  1098  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
  1099  	err := runDeploy(c, "bundle/wordpress-simple")
  1100  	c.Assert(err, jc.ErrorIsNil)
  1101  	application, err := s.State.Application("wordpress")
  1102  	c.Assert(err, jc.ErrorIsNil)
  1103  	ann, err := s.Model.Annotations(application)
  1104  	c.Assert(err, jc.ErrorIsNil)
  1105  	c.Assert(ann, jc.DeepEquals, map[string]string{"bundleURL": "cs:bundle/wordpress-simple-1"})
  1106  	application2, err := s.State.Application("mysql")
  1107  	c.Assert(err, jc.ErrorIsNil)
  1108  	ann2, err := s.Model.Annotations(application2)
  1109  	c.Assert(err, jc.ErrorIsNil)
  1110  	c.Assert(ann2, jc.DeepEquals, map[string]string{"bundleURL": "cs:bundle/wordpress-simple-1"})
  1111  }
  1112  
  1113  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationUpgrade(c *gc.C) {
  1114  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
  1115  	testcharms.UploadCharm(c, s.client, "vivid/upgrade-1", "upgrade1")
  1116  	_, ch := testcharms.UploadCharm(c, s.client, "vivid/upgrade-2", "upgrade2")
  1117  
  1118  	// First deploy the bundle.
  1119  	err := s.DeployBundleYAML(c, `
  1120          applications:
  1121              wordpress:
  1122                  charm: wordpress
  1123                  num_units: 1
  1124                  options:
  1125                      blog-title: these are the voyages
  1126                  constraints: spaces=final,frontiers mem=8000M
  1127              up:
  1128                  charm: vivid/upgrade-1
  1129                  num_units: 1
  1130                  constraints: mem=8G
  1131      `)
  1132  	c.Assert(err, jc.ErrorIsNil)
  1133  	s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:xenial/wordpress-42")
  1134  
  1135  	// Then deploy a new bundle with modified charm revision and options.
  1136  	stdOut, _, err := s.DeployBundleYAMLWithOutput(c, `
  1137          applications:
  1138              wordpress:
  1139                  charm: wordpress
  1140                  num_units: 1
  1141                  options:
  1142                      blog-title: new title
  1143                  constraints: spaces=new cores=8
  1144              up:
  1145                  charm: vivid/upgrade-2
  1146                  num_units: 1
  1147                  constraints: mem=8G
  1148      `)
  1149  	c.Assert(err, jc.ErrorIsNil)
  1150  	c.Assert(stdOut, gc.Equals, ""+
  1151  		"Executing changes:\n"+
  1152  		"- upload charm cs:vivid/upgrade-2 for series vivid\n"+
  1153  		"- upgrade up to use charm cs:vivid/upgrade-2 for series vivid\n"+
  1154  		"- set application options for wordpress\n"+
  1155  		`- set constraints for wordpress to "spaces=new cores=8"`,
  1156  	)
  1157  
  1158  	s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:vivid/upgrade-2", "cs:xenial/wordpress-42")
  1159  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
  1160  		"up": {
  1161  			charm:       "cs:vivid/upgrade-2",
  1162  			config:      ch.Config().DefaultSettings(),
  1163  			constraints: constraints.MustParse("mem=8G"),
  1164  		},
  1165  		"wordpress": {
  1166  			charm:       "cs:xenial/wordpress-42",
  1167  			config:      s.combinedSettings(wpch, charm.Settings{"blog-title": "new title"}),
  1168  			constraints: constraints.MustParse("spaces=new cores=8"),
  1169  		},
  1170  	})
  1171  	s.assertUnitsCreated(c, map[string]string{
  1172  		"up/0":        "0",
  1173  		"wordpress/0": "1",
  1174  	})
  1175  }
  1176  
  1177  func (s *BundleDeployCharmStoreSuite) TestDeployBundleExpose(c *gc.C) {
  1178  	_, ch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
  1179  	content := `
  1180          applications:
  1181              wordpress:
  1182                  charm: wordpress
  1183                  num_units: 1
  1184                  expose: true
  1185      `
  1186  	expectedApplications := map[string]applicationInfo{
  1187  		"wordpress": {
  1188  			charm:   "cs:xenial/wordpress-42",
  1189  			config:  ch.Config().DefaultSettings(),
  1190  			exposed: true,
  1191  		},
  1192  	}
  1193  
  1194  	// First deploy the bundle.
  1195  	err := s.DeployBundleYAML(c, content)
  1196  	c.Assert(err, jc.ErrorIsNil)
  1197  	s.assertApplicationsDeployed(c, expectedApplications)
  1198  
  1199  	// Then deploy the same bundle again: no error is produced when the application
  1200  	// is exposed again.
  1201  	stdOut, _, err := s.DeployBundleYAMLWithOutput(c, content)
  1202  	c.Assert(err, jc.ErrorIsNil)
  1203  	s.assertApplicationsDeployed(c, expectedApplications)
  1204  	c.Check(stdOut, gc.Equals, "") // Nothing to do.
  1205  
  1206  	// Then deploy a bundle with the application unexposed, and check that the
  1207  	// application is not unexposed.
  1208  	stdOut, _, err = s.DeployBundleYAMLWithOutput(c, `
  1209          applications:
  1210              wordpress:
  1211                  charm: wordpress
  1212                  num_units: 1
  1213                  expose: false
  1214      `)
  1215  	c.Assert(err, jc.ErrorIsNil)
  1216  	s.assertApplicationsDeployed(c, expectedApplications)
  1217  	c.Check(stdOut, gc.Equals, "") // Nothing to do.
  1218  }
  1219  
  1220  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationUpgradeFailure(c *gc.C) {
  1221  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1222  
  1223  	// Try upgrading to a different series.
  1224  	// Note that this test comes before the next one because
  1225  	// otherwise we can't resolve the charm URL because the charm's
  1226  	// "base entity" is not marked as promulgated so the query by
  1227  	// promulgated will find it.
  1228  	testcharms.UploadCharm(c, s.client, "vivid/wordpress-42", "wordpress")
  1229  	err := s.DeployBundleYAML(c, `
  1230          applications:
  1231              wordpress:
  1232                  charm: vivid/wordpress
  1233                  num_units: 1
  1234      `)
  1235  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade application "wordpress" to charm "cs:vivid/wordpress-42": cannot change an application's series`)
  1236  }
  1237  
  1238  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMultipleRelations(c *gc.C) {
  1239  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1240  	testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql")
  1241  	testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql")
  1242  	testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish")
  1243  	err := s.DeployBundleYAML(c, `
  1244          applications:
  1245              wp:
  1246                  charm: wordpress
  1247                  num_units: 1
  1248              mysql:
  1249                  charm: mysql
  1250                  num_units: 1
  1251              pgres:
  1252                  charm: xenial/postgres-2
  1253                  num_units: 1
  1254              varnish:
  1255                  charm: xenial/varnish
  1256                  num_units: 1
  1257          relations:
  1258              - ["wp:db", "mysql:server"]
  1259              - ["wp:db", "pgres:server"]
  1260              - ["varnish:webcache", "wp:cache"]
  1261      `)
  1262  	c.Assert(err, jc.ErrorIsNil)
  1263  	s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:db pgres:server", "wp:cache varnish:webcache")
  1264  	s.assertUnitsCreated(c, map[string]string{
  1265  		"mysql/0":   "0",
  1266  		"pgres/0":   "1",
  1267  		"varnish/0": "2",
  1268  		"wp/0":      "3",
  1269  	})
  1270  }
  1271  
  1272  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNewRelations(c *gc.C) {
  1273  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1274  	testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql")
  1275  	testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql")
  1276  	testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish")
  1277  	err := s.DeployBundleYAML(c, `
  1278          applications:
  1279              wp:
  1280                  charm: wordpress
  1281                  num_units: 1
  1282              mysql:
  1283                  charm: mysql
  1284                  num_units: 1
  1285              varnish:
  1286                  charm: xenial/varnish
  1287                  num_units: 1
  1288          relations:
  1289              - ["wp:db", "mysql:server"]
  1290      `)
  1291  	c.Assert(err, jc.ErrorIsNil)
  1292  	stdOut, _, err := s.DeployBundleYAMLWithOutput(c, `
  1293          applications:
  1294              wp:
  1295                  charm: wordpress
  1296                  num_units: 1
  1297              mysql:
  1298                  charm: mysql
  1299                  num_units: 1
  1300              varnish:
  1301                  charm: xenial/varnish
  1302                  num_units: 1
  1303          relations:
  1304              - ["wp:db", "mysql:server"]
  1305              - ["varnish:webcache", "wp:cache"]
  1306      `)
  1307  	c.Assert(err, jc.ErrorIsNil)
  1308  	c.Assert(stdOut, gc.Equals, ""+
  1309  		"Executing changes:\n"+
  1310  		"- add relation varnish:webcache - wp:cache",
  1311  	)
  1312  	s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:cache varnish:webcache")
  1313  	s.assertUnitsCreated(c, map[string]string{
  1314  		"mysql/0":   "0",
  1315  		"varnish/0": "1",
  1316  		"wp/0":      "2",
  1317  	})
  1318  }
  1319  
  1320  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachinesUnitsPlacement(c *gc.C) {
  1321  	_, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1322  	_, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-2", "mysql")
  1323  
  1324  	content := `
  1325          applications:
  1326              wp:
  1327                  charm: cs:xenial/wordpress-0
  1328                  num_units: 2
  1329                  to:
  1330                      - 1
  1331                      - lxd:2
  1332                  options:
  1333                      blog-title: these are the voyages
  1334              sql:
  1335                  charm: cs:xenial/mysql
  1336                  num_units: 2
  1337                  to:
  1338                      - lxd:wp/0
  1339                      - new
  1340          machines:
  1341              1:
  1342                  series: xenial
  1343              2:
  1344      `
  1345  	err := s.DeployBundleYAML(c, content)
  1346  	c.Assert(err, jc.ErrorIsNil)
  1347  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
  1348  		"sql": {charm: "cs:xenial/mysql-2", config: mysqlch.Config().DefaultSettings()},
  1349  		"wp": {
  1350  			charm:  "cs:xenial/wordpress-0",
  1351  			config: s.combinedSettings(wpch, charm.Settings{"blog-title": "these are the voyages"}),
  1352  		},
  1353  	})
  1354  	s.assertRelationsEstablished(c)
  1355  
  1356  	// We explicitly pull out the map creation in the call to
  1357  	// s.assertUnitsCreated() and create the map as a new variable
  1358  	// because this /appears/ to tickle a bug on ppc64le using
  1359  	// gccgo-4.9; the bug is that the map on the receiving side
  1360  	// does not have the same contents as it does here - which is
  1361  	// weird because that pattern is used elsewhere in this
  1362  	// function. And just pulling the map instantiation out of the
  1363  	// call is not enough; we need to do something benign with the
  1364  	// variable to keep a reference beyond the call to the
  1365  	// s.assertUnitsCreated(). I have to chosen to delete a
  1366  	// non-existent key. This problem does not occur on amd64
  1367  	// using gc or gccgo-4.9. Nor does it happen using go1.6 on
  1368  	// ppc64. Once we switch to go1.6 across the board this change
  1369  	// should be reverted. See http://pad.lv/1556116.
  1370  	expectedUnits := map[string]string{
  1371  		"sql/0": "0/lxd/0",
  1372  		"sql/1": "2",
  1373  		"wp/0":  "0",
  1374  		"wp/1":  "1/lxd/0",
  1375  	}
  1376  	s.assertUnitsCreated(c, expectedUnits)
  1377  	delete(expectedUnits, "non-existent")
  1378  
  1379  	// Redeploy the same bundle again.
  1380  	err = s.DeployBundleYAML(c, content)
  1381  	c.Assert(err, jc.ErrorIsNil)
  1382  	s.assertUnitsCreated(c, map[string]string{
  1383  		"sql/0": "0/lxd/0",
  1384  		"sql/1": "2",
  1385  		"wp/0":  "0",
  1386  		"wp/1":  "1/lxd/0",
  1387  	})
  1388  }
  1389  
  1390  func (s *BundleDeployCharmStoreSuite) TestLXCTreatedAsLXD(c *gc.C) {
  1391  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1392  
  1393  	// Note that we use lxc here, to represent a 1.x bundle that specifies lxc.
  1394  	content := `
  1395          applications:
  1396              wp:
  1397                  charm: cs:xenial/wordpress-0
  1398                  num_units: 1
  1399                  to:
  1400                      - lxc:0
  1401                  options:
  1402                      blog-title: these are the voyages
  1403              wp2:
  1404                  charm: cs:xenial/wordpress-0
  1405                  num_units: 1
  1406                  to:
  1407                      - lxc:0
  1408                  options:
  1409                      blog-title: these are the voyages
  1410          machines:
  1411              0:
  1412                  series: xenial
  1413      `
  1414  	_, output, err := s.DeployBundleYAMLWithOutput(c, content)
  1415  	c.Assert(err, jc.ErrorIsNil)
  1416  	expectedUnits := map[string]string{
  1417  		"wp/0":  "0/lxd/0",
  1418  		"wp2/0": "0/lxd/1",
  1419  	}
  1420  	idx := strings.Index(output, "Bundle has one or more containers specified as lxc. lxc containers are deprecated in Juju 2.0. lxd containers will be deployed instead.")
  1421  	lastIdx := strings.LastIndex(output, "Bundle has one or more containers specified as lxc. lxc containers are deprecated in Juju 2.0. lxd containers will be deployed instead.")
  1422  	// The message exists.
  1423  	c.Assert(idx, jc.GreaterThan, -1)
  1424  	// No more than one instance of the message was printed.
  1425  	c.Assert(idx, gc.Equals, lastIdx)
  1426  	s.assertUnitsCreated(c, expectedUnits)
  1427  }
  1428  
  1429  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachineAttributes(c *gc.C) {
  1430  	_, ch := testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1431  	err := s.DeployBundleYAML(c, `
  1432          applications:
  1433              django:
  1434                  charm: cs:xenial/django-42
  1435                  num_units: 2
  1436                  to:
  1437                      - 1
  1438                      - new
  1439          machines:
  1440              1:
  1441                  series: xenial
  1442                  constraints: "cores=4 mem=4G"
  1443                  annotations:
  1444                      foo: bar
  1445      `)
  1446  	c.Assert(err, jc.ErrorIsNil)
  1447  	s.assertApplicationsDeployed(c, map[string]applicationInfo{
  1448  		"django": {charm: "cs:xenial/django-42", config: ch.Config().DefaultSettings()},
  1449  	})
  1450  	s.assertRelationsEstablished(c)
  1451  	s.assertUnitsCreated(c, map[string]string{
  1452  		"django/0": "0",
  1453  		"django/1": "1",
  1454  	})
  1455  	m, err := s.State.Machine("0")
  1456  	c.Assert(err, jc.ErrorIsNil)
  1457  	c.Assert(m.Series(), gc.Equals, "xenial")
  1458  	cons, err := m.Constraints()
  1459  	c.Assert(err, jc.ErrorIsNil)
  1460  	expectedCons, err := constraints.Parse("cores=4 mem=4G")
  1461  	c.Assert(err, jc.ErrorIsNil)
  1462  	c.Assert(cons, jc.DeepEquals, expectedCons)
  1463  	ann, err := s.Model.Annotations(m)
  1464  	c.Assert(err, jc.ErrorIsNil)
  1465  	c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"})
  1466  }
  1467  
  1468  func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwiceScaleUp(c *gc.C) {
  1469  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1470  	err := s.DeployBundleYAML(c, `
  1471          applications:
  1472              django:
  1473                  charm: cs:xenial/django-42
  1474                  num_units: 2
  1475      `)
  1476  	c.Assert(err, jc.ErrorIsNil)
  1477  	err = s.DeployBundleYAML(c, `
  1478          applications:
  1479              django:
  1480                  charm: cs:xenial/django-42
  1481                  num_units: 5
  1482      `)
  1483  	c.Assert(err, jc.ErrorIsNil)
  1484  	s.assertUnitsCreated(c, map[string]string{
  1485  		"django/0": "0",
  1486  		"django/1": "1",
  1487  		"django/2": "2",
  1488  		"django/3": "3",
  1489  		"django/4": "4",
  1490  	})
  1491  }
  1492  
  1493  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedInApplication(c *gc.C) {
  1494  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1495  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1496  	err := s.DeployBundleYAML(c, `
  1497          applications:
  1498              wordpress:
  1499                  charm: wordpress
  1500                  num_units: 3
  1501              django:
  1502                  charm: cs:xenial/django-42
  1503                  num_units: 2
  1504                  to: [wordpress]
  1505      `)
  1506  	c.Assert(err, jc.ErrorIsNil)
  1507  	s.assertUnitsCreated(c, map[string]string{
  1508  		"django/0":    "0",
  1509  		"django/1":    "1",
  1510  		"wordpress/0": "0",
  1511  		"wordpress/1": "1",
  1512  		"wordpress/2": "2",
  1513  	})
  1514  }
  1515  
  1516  func (s *BundleDeployCharmStoreSuite) TestDeployBundlePeerContainer(c *gc.C) {
  1517  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1518  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1519  
  1520  	stdOut, _, err := s.DeployBundleYAMLWithOutput(c, `
  1521          applications:
  1522              wordpress:
  1523                  charm: wordpress
  1524                  num_units: 2
  1525                  to: ["lxd:new"]
  1526              django:
  1527                  charm: cs:xenial/django-42
  1528                  num_units: 2
  1529                  to: ["lxd:wordpress"]
  1530      `)
  1531  	c.Assert(err, jc.ErrorIsNil)
  1532  	c.Check(stdOut, gc.Equals, ""+
  1533  		"Executing changes:\n"+
  1534  		"- upload charm cs:xenial/django-42 for series xenial\n"+
  1535  		"- deploy application django on xenial using cs:xenial/django-42\n"+
  1536  		"- upload charm cs:xenial/wordpress-0 for series xenial\n"+
  1537  		"- deploy application wordpress on xenial using cs:xenial/wordpress-0\n"+
  1538  		"- add lxd container 0/lxd/0 on new machine 0\n"+
  1539  		"- add lxd container 1/lxd/0 on new machine 1\n"+
  1540  		"- add unit wordpress/0 to 0/lxd/0\n"+
  1541  		"- add unit wordpress/1 to 1/lxd/0\n"+
  1542  		"- add lxd container 0/lxd/1 on new machine 0\n"+
  1543  		"- add lxd container 1/lxd/1 on new machine 1\n"+
  1544  		"- add unit django/0 to 0/lxd/1 to satisfy [lxd:wordpress]\n"+
  1545  		"- add unit django/1 to 1/lxd/1 to satisfy [lxd:wordpress]",
  1546  	)
  1547  
  1548  	s.assertUnitsCreated(c, map[string]string{
  1549  		"django/0":    "0/lxd/1",
  1550  		"django/1":    "1/lxd/1",
  1551  		"wordpress/0": "0/lxd/0",
  1552  		"wordpress/1": "1/lxd/0",
  1553  	})
  1554  }
  1555  
  1556  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitColocationWithUnit(c *gc.C) {
  1557  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1558  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1559  	testcharms.UploadCharm(c, s.client, "xenial/rails-0", "dummy")
  1560  	err := s.DeployBundleYAML(c, `
  1561          applications:
  1562              memcached:
  1563                  charm: cs:xenial/mem-47
  1564                  num_units: 3
  1565                  to: [1, new]
  1566              django:
  1567                  charm: cs:xenial/django-42
  1568                  num_units: 5
  1569                  to:
  1570                      - memcached/0
  1571                      - lxd:memcached/1
  1572                      - lxd:memcached/2
  1573                      - kvm:ror
  1574              ror:
  1575                  charm: rails
  1576                  num_units: 2
  1577                  to:
  1578                      - new
  1579                      - 1
  1580          machines:
  1581              1:
  1582                  series: xenial
  1583      `)
  1584  	c.Assert(err, jc.ErrorIsNil)
  1585  	s.assertUnitsCreated(c, map[string]string{
  1586  		"django/0":    "0",
  1587  		"django/1":    "1/lxd/0",
  1588  		"django/2":    "2/lxd/0",
  1589  		"django/3":    "3/kvm/0",
  1590  		"django/4":    "0/kvm/0",
  1591  		"memcached/0": "0",
  1592  		"memcached/1": "1",
  1593  		"memcached/2": "2",
  1594  		"ror/0":       "3",
  1595  		"ror/1":       "0",
  1596  	})
  1597  }
  1598  
  1599  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedToMachines(c *gc.C) {
  1600  	testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy")
  1601  	err := s.DeployBundleYAML(c, `
  1602          applications:
  1603              django:
  1604                  charm: cs:django
  1605                  num_units: 7
  1606                  to:
  1607                      - new
  1608                      - 4
  1609                      - kvm:8
  1610                      - lxd:4
  1611                      - lxd:4
  1612                      - lxd:new
  1613          machines:
  1614              4:
  1615              8:
  1616      `)
  1617  	c.Assert(err, jc.ErrorIsNil)
  1618  	s.assertUnitsCreated(c, map[string]string{
  1619  		"django/0": "2",       // Machine "new" in the bundle.
  1620  		"django/1": "0",       // Machine "4" in the bundle.
  1621  		"django/2": "1/kvm/0", // The KVM container in bundle machine "8".
  1622  		"django/3": "0/lxd/0", // First lxd container in bundle machine "4".
  1623  		"django/4": "0/lxd/1", // Second lxd container in bundle machine "4".
  1624  		"django/5": "3/lxd/0", // First lxd in new machine.
  1625  		"django/6": "4/lxd/0", // Second lxd in new machine.
  1626  	})
  1627  }
  1628  
  1629  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMassiveUnitColocation(c *gc.C) {
  1630  	testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy")
  1631  	testcharms.UploadCharm(c, s.client, "bionic/mem-47", "dummy")
  1632  	testcharms.UploadCharm(c, s.client, "bionic/rails-0", "dummy")
  1633  	err := s.DeployBundleYAML(c, `
  1634          applications:
  1635              memcached:
  1636                  charm: cs:bionic/mem-47
  1637                  num_units: 3
  1638                  to: [1, 2, 3]
  1639              django:
  1640                  charm: cs:bionic/django-42
  1641                  num_units: 4
  1642                  to:
  1643                      - 1
  1644                      - lxd:memcached
  1645              ror:
  1646                  charm: rails
  1647                  num_units: 3
  1648                  to:
  1649                      - 1
  1650                      - kvm:3
  1651          machines:
  1652              1:
  1653              2:
  1654              3:
  1655      `)
  1656  	c.Assert(err, jc.ErrorIsNil)
  1657  	s.assertUnitsCreated(c, map[string]string{
  1658  		"django/0":    "0",
  1659  		"django/1":    "0/lxd/0",
  1660  		"django/2":    "1/lxd/0",
  1661  		"django/3":    "2/lxd/0",
  1662  		"memcached/0": "0",
  1663  		"memcached/1": "1",
  1664  		"memcached/2": "2",
  1665  		"ror/0":       "0",
  1666  		"ror/1":       "2/kvm/0",
  1667  		"ror/2":       "3",
  1668  	})
  1669  
  1670  	// Redeploy a very similar bundle with another application unit. The new unit
  1671  	// is placed on the first unit of memcached. Due to ordering of the applications
  1672  	// there is no deterministic way to determine "least crowded" in a meaningful way.
  1673  	content := `
  1674          applications:
  1675              memcached:
  1676                  charm: cs:bionic/mem-47
  1677                  num_units: 3
  1678                  to: [1, 2, 3]
  1679              django:
  1680                  charm: cs:bionic/django-42
  1681                  num_units: 4
  1682                  to:
  1683                      - 1
  1684                      - lxd:memcached
  1685              node:
  1686                  charm: cs:bionic/django-42
  1687                  num_units: 1
  1688                  to:
  1689                      - lxd:memcached
  1690          machines:
  1691              1:
  1692              2:
  1693              3:
  1694      `
  1695  	stdOut, _, err := s.DeployBundleYAMLWithOutput(c, content)
  1696  	c.Assert(err, jc.ErrorIsNil)
  1697  	c.Check(stdOut, gc.Equals, ""+
  1698  		"Executing changes:\n"+
  1699  		"- deploy application node on bionic using cs:bionic/django-42\n"+
  1700  		"- add unit node/0 to 0/lxd/0 to satisfy [lxd:memcached]",
  1701  	)
  1702  
  1703  	// Redeploy the same bundle again and check that nothing happens.
  1704  	stdOut, _, err = s.DeployBundleYAMLWithOutput(c, content)
  1705  	c.Assert(err, jc.ErrorIsNil)
  1706  	c.Assert(stdOut, gc.Equals, "")
  1707  	s.assertUnitsCreated(c, map[string]string{
  1708  		"django/0":    "0",
  1709  		"django/1":    "0/lxd/0",
  1710  		"django/2":    "1/lxd/0",
  1711  		"django/3":    "2/lxd/0",
  1712  		"memcached/0": "0",
  1713  		"memcached/1": "1",
  1714  		"memcached/2": "2",
  1715  		"node/0":      "0/lxd/1",
  1716  		"ror/0":       "0",
  1717  		"ror/1":       "2/kvm/0",
  1718  		"ror/2":       "3",
  1719  	})
  1720  }
  1721  
  1722  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWithAnnotations_OutputIsCorrect(c *gc.C) {
  1723  	testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy")
  1724  	testcharms.UploadCharm(c, s.client, "bionic/mem-47", "dummy")
  1725  	stdOut, stdErr, err := s.DeployBundleYAMLWithOutput(c, `
  1726          applications:
  1727              django:
  1728                  charm: cs:django
  1729                  num_units: 1
  1730                  annotations:
  1731                      key1: value1
  1732                      key2: value2
  1733                  to: [1]
  1734              memcached:
  1735                  charm: bionic/mem-47
  1736                  num_units: 1
  1737          machines:
  1738              1:
  1739                  annotations: {foo: bar}
  1740      `)
  1741  	c.Assert(err, jc.ErrorIsNil)
  1742  
  1743  	c.Check(stdOut, gc.Equals, ""+
  1744  		"Executing changes:\n"+
  1745  		"- upload charm cs:bionic/django-42 for series bionic\n"+
  1746  		"- deploy application django on bionic using cs:bionic/django-42\n"+
  1747  		"- set annotations for django\n"+
  1748  		"- upload charm cs:bionic/mem-47 for series bionic\n"+
  1749  		"- deploy application memcached on bionic using cs:bionic/mem-47\n"+
  1750  		"- add new machine 0 (bundle machine 1)\n"+
  1751  		"- set annotations for new machine 0\n"+
  1752  		"- add unit django/0 to new machine 0\n"+
  1753  		"- add unit memcached/0 to new machine 1",
  1754  	)
  1755  	c.Check(stdErr, gc.Equals, ""+
  1756  		"Resolving charm: cs:django\n"+
  1757  		"Resolving charm: bionic/mem-47\n"+
  1758  		"Deploy of bundle completed.",
  1759  	)
  1760  }
  1761  
  1762  func (s *BundleDeployCharmStoreSuite) TestDeployBundleAnnotations(c *gc.C) {
  1763  	testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy")
  1764  	testcharms.UploadCharm(c, s.client, "bionic/mem-47", "dummy")
  1765  	err := s.DeployBundleYAML(c, `
  1766          applications:
  1767              django:
  1768                  charm: cs:django
  1769                  num_units: 1
  1770                  annotations:
  1771                      key1: value1
  1772                      key2: value2
  1773                  to: [1]
  1774              memcached:
  1775                  charm: bionic/mem-47
  1776                  num_units: 1
  1777          machines:
  1778              1:
  1779                  annotations: {foo: bar}
  1780      `)
  1781  	c.Assert(err, jc.ErrorIsNil)
  1782  	svc, err := s.State.Application("django")
  1783  	c.Assert(err, jc.ErrorIsNil)
  1784  	ann, err := s.Model.Annotations(svc)
  1785  	c.Assert(err, jc.ErrorIsNil)
  1786  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1787  		"key1": "value1",
  1788  		"key2": "value2",
  1789  	})
  1790  	m, err := s.State.Machine("0")
  1791  	c.Assert(err, jc.ErrorIsNil)
  1792  	ann, err = s.Model.Annotations(m)
  1793  	c.Assert(err, jc.ErrorIsNil)
  1794  	c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"})
  1795  
  1796  	// Update the annotations and deploy the bundle again.
  1797  	err = s.DeployBundleYAML(c, `
  1798          applications:
  1799              django:
  1800                  charm: cs:django
  1801                  num_units: 1
  1802                  annotations:
  1803                      key1: new value!
  1804                      key2: value2
  1805                  to: [1]
  1806          machines:
  1807              1:
  1808                  annotations: {answer: 42}
  1809      `)
  1810  	c.Assert(err, jc.ErrorIsNil)
  1811  	ann, err = s.Model.Annotations(svc)
  1812  	c.Assert(err, jc.ErrorIsNil)
  1813  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1814  		"key1": "new value!",
  1815  		"key2": "value2",
  1816  	})
  1817  	ann, err = s.Model.Annotations(m)
  1818  	c.Assert(err, jc.ErrorIsNil)
  1819  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1820  		"foo":    "bar",
  1821  		"answer": "42",
  1822  	})
  1823  }
  1824  
  1825  func (s *BundleDeployCharmStoreSuite) TestDeployBundleExistingMachines(c *gc.C) {
  1826  	xenialMachine := &factory.MachineParams{Series: "xenial"}
  1827  	s.Factory.MakeMachine(c, xenialMachine) // machine-0
  1828  	s.Factory.MakeMachine(c, xenialMachine) // machine-1
  1829  	s.Factory.MakeMachine(c, xenialMachine) // machine-2
  1830  	s.Factory.MakeMachine(c, xenialMachine) // machine-3
  1831  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1832  	err := s.DeployBundleYAML(c, `
  1833          applications:
  1834              django:
  1835                  charm: cs:django
  1836                  num_units: 3
  1837                  to: [0,1,2]
  1838          machines:
  1839              0:
  1840              1:
  1841              2:
  1842      `, "--map-machines", "existing,2=3")
  1843  	c.Assert(err, jc.ErrorIsNil)
  1844  	s.assertUnitsCreated(c, map[string]string{
  1845  		"django/0": "0",
  1846  		"django/1": "1",
  1847  		"django/2": "3",
  1848  	})
  1849  }
  1850  
  1851  type mockAllWatcher struct {
  1852  	next func() []multiwatcher.Delta
  1853  }
  1854  
  1855  func (w mockAllWatcher) Next() ([]multiwatcher.Delta, error) {
  1856  	return w.next(), nil
  1857  }
  1858  
  1859  func (mockAllWatcher) Stop() error {
  1860  	return nil
  1861  }
  1862  
  1863  type ProcessIncludesSuite struct {
  1864  	coretesting.BaseSuite
  1865  }
  1866  
  1867  var _ = gc.Suite(&ProcessIncludesSuite{})
  1868  
  1869  func (*ProcessIncludesSuite) TestNonString(c *gc.C) {
  1870  	value := 1234
  1871  	result, changed, err := processValue("", value)
  1872  
  1873  	c.Check(err, jc.ErrorIsNil)
  1874  	c.Check(changed, jc.IsFalse)
  1875  	c.Check(result, gc.Equals, value)
  1876  }
  1877  
  1878  func (*ProcessIncludesSuite) TestSimpleString(c *gc.C) {
  1879  	value := "simple"
  1880  	result, changed, err := processValue("", value)
  1881  
  1882  	c.Check(err, jc.ErrorIsNil)
  1883  	c.Check(changed, jc.IsFalse)
  1884  	c.Check(result, gc.Equals, value)
  1885  }
  1886  
  1887  func (*ProcessIncludesSuite) TestMissingFile(c *gc.C) {
  1888  	value := "include-file://simple"
  1889  	result, changed, err := processValue("", value)
  1890  
  1891  	c.Check(err, gc.ErrorMatches, "unable to read file: "+missingFileRegex("simple"))
  1892  	c.Check(changed, jc.IsFalse)
  1893  	c.Check(result, gc.IsNil)
  1894  }
  1895  
  1896  func (*ProcessIncludesSuite) TestFileNameIsInDir(c *gc.C) {
  1897  	dir := c.MkDir()
  1898  	filename := filepath.Join(dir, "content")
  1899  	err := ioutil.WriteFile(filename, []byte("testing"), 0644)
  1900  	c.Assert(err, jc.ErrorIsNil)
  1901  
  1902  	value := "include-file://content"
  1903  	result, changed, err := processValue(dir, value)
  1904  
  1905  	c.Assert(err, jc.ErrorIsNil)
  1906  	c.Check(changed, jc.IsTrue)
  1907  	c.Check(result, gc.Equals, "testing")
  1908  }
  1909  
  1910  func (*ProcessIncludesSuite) TestRelativePath(c *gc.C) {
  1911  	dir := c.MkDir()
  1912  	c.Assert(os.Mkdir(filepath.Join(dir, "nested"), 0755), jc.ErrorIsNil)
  1913  
  1914  	filename := filepath.Join(dir, "nested", "content")
  1915  	err := ioutil.WriteFile(filename, []byte("testing"), 0644)
  1916  	c.Assert(err, jc.ErrorIsNil)
  1917  
  1918  	value := "include-file://./nested/content"
  1919  	result, changed, err := processValue(dir, value)
  1920  
  1921  	c.Assert(err, jc.ErrorIsNil)
  1922  	c.Check(changed, jc.IsTrue)
  1923  	c.Check(result, gc.Equals, "testing")
  1924  }
  1925  
  1926  func (*ProcessIncludesSuite) TestAbsolutePath(c *gc.C) {
  1927  	dir := c.MkDir()
  1928  	c.Assert(os.Mkdir(filepath.Join(dir, "nested"), 0755), jc.ErrorIsNil)
  1929  
  1930  	filename := filepath.Join(dir, "nested", "content")
  1931  	c.Check(filepath.IsAbs(filename), jc.IsTrue)
  1932  	err := ioutil.WriteFile(filename, []byte("testing"), 0644)
  1933  	c.Assert(err, jc.ErrorIsNil)
  1934  
  1935  	value := "include-file://" + filename
  1936  	result, changed, err := processValue(dir, value)
  1937  
  1938  	c.Assert(err, jc.ErrorIsNil)
  1939  	c.Check(changed, jc.IsTrue)
  1940  	c.Check(result, gc.Equals, "testing")
  1941  }
  1942  
  1943  func (*ProcessIncludesSuite) TestBase64Encode(c *gc.C) {
  1944  	dir := c.MkDir()
  1945  	filename := filepath.Join(dir, "content")
  1946  	err := ioutil.WriteFile(filename, []byte("testing"), 0644)
  1947  	c.Assert(err, jc.ErrorIsNil)
  1948  
  1949  	value := "include-base64://content"
  1950  	result, changed, err := processValue(dir, value)
  1951  
  1952  	c.Assert(err, jc.ErrorIsNil)
  1953  	c.Check(changed, jc.IsTrue)
  1954  	encoded := base64.StdEncoding.EncodeToString([]byte("testing"))
  1955  	c.Check(result, gc.Equals, encoded)
  1956  }
  1957  
  1958  func (*ProcessIncludesSuite) TestBundleReplacements(c *gc.C) {
  1959  	bundleYAML := `
  1960          applications:
  1961              django:
  1962                  charm: cs:django
  1963                  num_units: 1
  1964                  options:
  1965                      private: include-base64://sekrit.binary
  1966                  annotations:
  1967                      key1: value1
  1968                      key2: value2
  1969                      key3: include-file://annotation
  1970                  to: [1]
  1971              memcached:
  1972                  charm: xenial/mem-47
  1973                  num_units: 1
  1974          machines:
  1975              1:
  1976                  annotations: {foo: bar, baz: "include-file://machine" }
  1977              2:
  1978      `
  1979  
  1980  	baseDir := c.MkDir()
  1981  	bundleFile := filepath.Join(baseDir, "bundle.yaml")
  1982  	err := ioutil.WriteFile(bundleFile, []byte(bundleYAML), 0644)
  1983  	c.Assert(err, jc.ErrorIsNil)
  1984  
  1985  	c.Assert(
  1986  		ioutil.WriteFile(
  1987  			filepath.Join(baseDir, "sekrit.binary"),
  1988  			[]byte{42, 12, 0, 23, 8}, 0644),
  1989  		jc.ErrorIsNil)
  1990  	c.Assert(
  1991  		ioutil.WriteFile(
  1992  			filepath.Join(baseDir, "annotation"),
  1993  			[]byte("value3"), 0644),
  1994  		jc.ErrorIsNil)
  1995  	c.Assert(
  1996  		ioutil.WriteFile(
  1997  			filepath.Join(baseDir, "machine"),
  1998  			[]byte("wibble"), 0644),
  1999  		jc.ErrorIsNil)
  2000  
  2001  	bundleData, err := charmrepo.ReadBundleFile(bundleFile)
  2002  	c.Assert(err, jc.ErrorIsNil)
  2003  
  2004  	err = processBundleIncludes(baseDir, bundleData)
  2005  	c.Assert(err, jc.ErrorIsNil)
  2006  
  2007  	django := bundleData.Applications["django"]
  2008  	c.Check(django.Annotations["key1"], gc.Equals, "value1")
  2009  	c.Check(django.Annotations["key2"], gc.Equals, "value2")
  2010  	c.Check(django.Annotations["key3"], gc.Equals, "value3")
  2011  	c.Check(django.Options["private"], gc.Equals, "KgwAFwg=")
  2012  	annotations := bundleData.Machines["1"].Annotations
  2013  	c.Check(annotations["foo"], gc.Equals, "bar")
  2014  	c.Check(annotations["baz"], gc.Equals, "wibble")
  2015  }
  2016  
  2017  type ProcessBundleOverlaySuite struct {
  2018  	coretesting.BaseSuite
  2019  
  2020  	bundleData *charm.BundleData
  2021  }
  2022  
  2023  var _ = gc.Suite(&ProcessBundleOverlaySuite{})
  2024  
  2025  func (s *ProcessBundleOverlaySuite) SetUpTest(c *gc.C) {
  2026  	s.BaseSuite.SetUpTest(c)
  2027  
  2028  	baseBundle := `
  2029          applications:
  2030              django:
  2031                  expose: true
  2032                  charm: cs:django
  2033                  num_units: 1
  2034                  options:
  2035                      general: good
  2036                  annotations:
  2037                      key1: value1
  2038                      key2: value2
  2039                  to: [1]
  2040              memcached:
  2041                  charm: xenial/mem-47
  2042                  num_units: 1
  2043                  options:
  2044                      key: value
  2045          relations:
  2046              - - "django"
  2047                - "memcached"
  2048          machines:
  2049              1:
  2050                  annotations: {foo: bar}`
  2051  
  2052  	baseDir := c.MkDir()
  2053  	bundleFile := filepath.Join(baseDir, "bundle.yaml")
  2054  	err := ioutil.WriteFile(bundleFile, []byte(baseBundle), 0644)
  2055  	c.Assert(err, jc.ErrorIsNil)
  2056  
  2057  	s.bundleData, err = charmrepo.ReadBundleFile(bundleFile)
  2058  	c.Assert(err, jc.ErrorIsNil)
  2059  }
  2060  
  2061  func (s *ProcessBundleOverlaySuite) writeFile(c *gc.C, content string) string {
  2062  	// Write the content to a file in a new directoryt and return the file.
  2063  	baseDir := c.MkDir()
  2064  	filename := filepath.Join(baseDir, "config.yaml")
  2065  	err := ioutil.WriteFile(filename, []byte(content), 0644)
  2066  	c.Assert(err, jc.ErrorIsNil)
  2067  	return filename
  2068  }
  2069  
  2070  func (s *ProcessBundleOverlaySuite) TestNoFile(c *gc.C) {
  2071  	err := processBundleOverlay(s.bundleData)
  2072  	c.Assert(err, jc.ErrorIsNil)
  2073  }
  2074  
  2075  func (s *ProcessBundleOverlaySuite) TestBadFile(c *gc.C) {
  2076  	err := processBundleOverlay(s.bundleData, "bad")
  2077  	c.Assert(err, gc.ErrorMatches, `unable to read bundle overlay file ".*": bundle not found: .*bad`)
  2078  }
  2079  
  2080  func (s *ProcessBundleOverlaySuite) TestGoodYAML(c *gc.C) {
  2081  	filename := s.writeFile(c, "bad:\n\tindent")
  2082  	err := processBundleOverlay(s.bundleData, filename)
  2083  	c.Assert(err, gc.ErrorMatches, `unable to read bundle overlay file ".*": cannot unmarshal bundle data: yaml: line 2: found character that cannot start any token`)
  2084  }
  2085  
  2086  func (s *ProcessBundleOverlaySuite) TestReplaceZeroValues(c *gc.C) {
  2087  	config := `
  2088          applications:
  2089              django:
  2090                  expose: false
  2091                  num_units: 0
  2092      `
  2093  	filename := s.writeFile(c, config)
  2094  	err := processBundleOverlay(s.bundleData, filename)
  2095  	c.Assert(err, jc.ErrorIsNil)
  2096  	django := s.bundleData.Applications["django"]
  2097  
  2098  	c.Check(django.Expose, jc.IsFalse)
  2099  	c.Check(django.NumUnits, gc.Equals, 0)
  2100  }
  2101  
  2102  func (s *ProcessBundleOverlaySuite) TestMachineReplacement(c *gc.C) {
  2103  	config := `
  2104          machines:
  2105              1:
  2106              2:
  2107      `
  2108  	filename := s.writeFile(c, config)
  2109  	err := processBundleOverlay(s.bundleData, filename)
  2110  	c.Assert(err, jc.ErrorIsNil)
  2111  
  2112  	var machines []string
  2113  	for key := range s.bundleData.Machines {
  2114  		machines = append(machines, key)
  2115  	}
  2116  	sort.Strings(machines)
  2117  	c.Assert(machines, jc.DeepEquals, []string{"1", "2"})
  2118  }
  2119  
  2120  func (s *ProcessBundleOverlaySuite) assertApplications(c *gc.C, appNames ...string) {
  2121  	var applications []string
  2122  	for key := range s.bundleData.Applications {
  2123  		applications = append(applications, key)
  2124  	}
  2125  	sort.Strings(applications)
  2126  	sort.Strings(appNames)
  2127  	c.Assert(applications, jc.DeepEquals, appNames)
  2128  }
  2129  
  2130  func (s *ProcessBundleOverlaySuite) TestNewApplication(c *gc.C) {
  2131  	config := `
  2132          applications:
  2133              postgresql:
  2134                  charm: cs:postgresql
  2135                  num_units: 1
  2136          relations:
  2137              - - "postgresql:pgsql"
  2138                - "django:pgsql"
  2139      `
  2140  	filename := s.writeFile(c, config)
  2141  	err := processBundleOverlay(s.bundleData, filename)
  2142  	c.Assert(err, jc.ErrorIsNil)
  2143  	s.assertApplications(c, "django", "memcached", "postgresql")
  2144  	c.Assert(s.bundleData.Relations, jc.DeepEquals, [][]string{
  2145  		{"django", "memcached"},
  2146  		{"postgresql:pgsql", "django:pgsql"},
  2147  	})
  2148  }
  2149  
  2150  func (s *ProcessBundleOverlaySuite) TestRemoveApplication(c *gc.C) {
  2151  	config := `
  2152          applications:
  2153              memcached:
  2154      `
  2155  	filename := s.writeFile(c, config)
  2156  	err := processBundleOverlay(s.bundleData, filename)
  2157  	c.Assert(err, jc.ErrorIsNil)
  2158  	s.assertApplications(c, "django")
  2159  	c.Assert(s.bundleData.Relations, gc.HasLen, 0)
  2160  }
  2161  
  2162  func (s *ProcessBundleOverlaySuite) TestRemoveUnknownApplication(c *gc.C) {
  2163  	config := `
  2164          applications:
  2165              unknown:
  2166      `
  2167  	filename := s.writeFile(c, config)
  2168  	err := processBundleOverlay(s.bundleData, filename)
  2169  	c.Assert(err, jc.ErrorIsNil)
  2170  	s.assertApplications(c, "django", "memcached")
  2171  	c.Assert(s.bundleData.Relations, jc.DeepEquals, [][]string{
  2172  		{"django", "memcached"},
  2173  	})
  2174  }
  2175  
  2176  func (s *ProcessBundleOverlaySuite) TestIncludes(c *gc.C) {
  2177  	config := `
  2178          applications:
  2179              django:
  2180                  options:
  2181                      private: include-base64://sekrit.binary
  2182                  annotations:
  2183                      key3: include-file://annotation
  2184      `
  2185  	filename := s.writeFile(c, config)
  2186  	baseDir := filepath.Dir(filename)
  2187  
  2188  	c.Assert(
  2189  		ioutil.WriteFile(
  2190  			filepath.Join(baseDir, "sekrit.binary"),
  2191  			[]byte{42, 12, 0, 23, 8}, 0644),
  2192  		jc.ErrorIsNil)
  2193  	c.Assert(
  2194  		ioutil.WriteFile(
  2195  			filepath.Join(baseDir, "annotation"),
  2196  			[]byte("value3"), 0644),
  2197  		jc.ErrorIsNil)
  2198  
  2199  	err := processBundleOverlay(s.bundleData, filename)
  2200  	c.Assert(err, jc.ErrorIsNil)
  2201  	django := s.bundleData.Applications["django"]
  2202  	c.Check(django.Annotations, jc.DeepEquals, map[string]string{
  2203  		"key1": "value1",
  2204  		"key2": "value2",
  2205  		"key3": "value3"})
  2206  	c.Check(django.Options, jc.DeepEquals, map[string]interface{}{
  2207  		"general": "good",
  2208  		"private": "KgwAFwg="})
  2209  }
  2210  
  2211  func (s *ProcessBundleOverlaySuite) TestRemainingFields(c *gc.C) {
  2212  	// Note that we don't care about the actual values here
  2213  	// as bundle validation is done after replacement.
  2214  	config := `
  2215          applications:
  2216              django:
  2217                  charm: cs:django-23
  2218                  series: wisty
  2219                  resources:
  2220                      something: or other
  2221                  to:
  2222                  - 3
  2223                  constraints: big machine
  2224                  storage:
  2225                      disk: big
  2226                  devices:
  2227                      gpu: 1,nvidia.com/gpu
  2228                  bindings:
  2229                      where: dmz
  2230      `
  2231  	filename := s.writeFile(c, config)
  2232  	err := processBundleOverlay(s.bundleData, filename)
  2233  	c.Assert(err, jc.ErrorIsNil)
  2234  	django := s.bundleData.Applications["django"]
  2235  
  2236  	c.Check(django.Charm, gc.Equals, "cs:django-23")
  2237  	c.Check(django.Series, gc.Equals, "wisty")
  2238  	c.Check(django.Resources, jc.DeepEquals, map[string]interface{}{
  2239  		"something": "or other"})
  2240  	c.Check(django.To, jc.DeepEquals, []string{"3"})
  2241  	c.Check(django.Constraints, gc.Equals, "big machine")
  2242  	c.Check(django.Storage, jc.DeepEquals, map[string]string{
  2243  		"disk": "big"})
  2244  	c.Check(django.Devices, jc.DeepEquals, map[string]string{
  2245  		"gpu": "1,nvidia.com/gpu"})
  2246  	c.Check(django.EndpointBindings, jc.DeepEquals, map[string]string{
  2247  		"where": "dmz"})
  2248  }
  2249  
  2250  func (s *ProcessBundleOverlaySuite) TestYAMLInterpolation(c *gc.C) {
  2251  
  2252  	removeDjango := s.writeFile(c, `
  2253  applications:
  2254      django:
  2255      `)
  2256  
  2257  	addWiki := s.writeFile(c, `
  2258  defaultwiki: &DEFAULTWIKI
  2259      charm: "cs:trusty/mediawiki-5"
  2260      num_units: 1
  2261      options: &WIKIOPTS
  2262          debug: false
  2263          name: Please set name of wiki
  2264          skin: vector
  2265  
  2266  applications:
  2267      wiki:
  2268          <<: *DEFAULTWIKI
  2269          options:
  2270              <<: *WIKIOPTS
  2271              name: The name override
  2272  relations:
  2273      - - "wiki"
  2274        - "memcached"
  2275  `)
  2276  
  2277  	err := processBundleOverlay(s.bundleData, removeDjango, addWiki)
  2278  	c.Assert(err, jc.ErrorIsNil)
  2279  
  2280  	s.assertApplications(c, "memcached", "wiki")
  2281  	c.Assert(s.bundleData.Relations, jc.DeepEquals, [][]string{
  2282  		{"wiki", "memcached"},
  2283  	})
  2284  	wiki := s.bundleData.Applications["wiki"]
  2285  	c.Assert(wiki.Charm, gc.Equals, "cs:trusty/mediawiki-5")
  2286  	c.Assert(wiki.Options, gc.DeepEquals,
  2287  		map[string]interface{}{
  2288  			"debug": false,
  2289  			"name":  "The name override",
  2290  			"skin":  "vector",
  2291  		})
  2292  }
  2293  
  2294  func (s *BundleDeployCharmStoreSuite) TestDeployBundlePassesSequences(c *gc.C) {
  2295  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  2296  
  2297  	// Deploy another django app with two units, this will bump the sequences
  2298  	// for machines and the django application. Then remove them both.
  2299  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{
  2300  		Name: "django"})
  2301  	u1 := s.Factory.MakeUnit(c, &factory.UnitParams{
  2302  		Application: app,
  2303  	})
  2304  	u2 := s.Factory.MakeUnit(c, &factory.UnitParams{
  2305  		Application: app,
  2306  	})
  2307  	var machines []*state.Machine
  2308  	var ids []string
  2309  	destroyUnitsMachine := func(u *state.Unit) {
  2310  		id, err := u.AssignedMachineId()
  2311  		c.Assert(err, jc.ErrorIsNil)
  2312  		ids = append(ids, id)
  2313  		m, err := s.State.Machine(id)
  2314  		c.Assert(err, jc.ErrorIsNil)
  2315  		machines = append(machines, m)
  2316  		c.Assert(m.ForceDestroy(), jc.ErrorIsNil)
  2317  	}
  2318  	// Tear them down. This is somewhat convoluted, but it is what we need
  2319  	// to do to properly cleanly tear down machines.
  2320  	c.Assert(app.Destroy(), jc.ErrorIsNil)
  2321  	destroyUnitsMachine(u1)
  2322  	destroyUnitsMachine(u2)
  2323  	c.Assert(s.State.Cleanup(), jc.ErrorIsNil)
  2324  	for _, m := range machines {
  2325  		c.Assert(m.EnsureDead(), jc.ErrorIsNil)
  2326  		c.Assert(m.MarkForRemoval(), jc.ErrorIsNil)
  2327  	}
  2328  	c.Assert(s.State.CompleteMachineRemovals(ids...), jc.ErrorIsNil)
  2329  
  2330  	apps, err := s.State.AllApplications()
  2331  	c.Assert(err, jc.ErrorIsNil)
  2332  	c.Assert(apps, gc.HasLen, 0)
  2333  	machines, err = s.State.AllMachines()
  2334  	c.Assert(err, jc.ErrorIsNil)
  2335  	c.Assert(machines, gc.HasLen, 0)
  2336  
  2337  	stdOut, _, err := s.DeployBundleYAMLWithOutput(c, `
  2338          applications:
  2339              django:
  2340                  charm: cs:xenial/django-42
  2341                  num_units: 2
  2342      `)
  2343  	c.Check(stdOut, gc.Equals, ""+
  2344  		"Executing changes:\n"+
  2345  		"- upload charm cs:xenial/django-42 for series xenial\n"+
  2346  		"- deploy application django on xenial using cs:xenial/django-42\n"+
  2347  		"- add unit django/2 to new machine 2\n"+
  2348  		"- add unit django/3 to new machine 3",
  2349  	)
  2350  	c.Assert(err, jc.ErrorIsNil)
  2351  	s.assertUnitsCreated(c, map[string]string{
  2352  		"django/2": "2",
  2353  		"django/3": "3",
  2354  	})
  2355  }
  2356  
  2357  type removeRelationsSuite struct{}
  2358  
  2359  var (
  2360  	_ = gc.Suite(&removeRelationsSuite{})
  2361  
  2362  	sampleRelations = [][]string{
  2363  		{"kubernetes-master:kube-control", "kubernetes-worker:kube-control"},
  2364  		{"kubernetes-master:etcd", "etcd:db"},
  2365  		{"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"},
  2366  		{"flannel", "etcd"}, // removed :endpoint
  2367  		{"flannel:cni", "kubernetes-master:cni"},
  2368  		{"flannel:cni", "kubernetes-worker:cni"},
  2369  	}
  2370  )
  2371  
  2372  func (*removeRelationsSuite) TestNil(c *gc.C) {
  2373  	result := removeRelations(nil, "foo")
  2374  	c.Assert(result, gc.HasLen, 0)
  2375  }
  2376  
  2377  func (*removeRelationsSuite) TestEmpty(c *gc.C) {
  2378  	result := removeRelations([][]string{}, "foo")
  2379  	c.Assert(result, gc.HasLen, 0)
  2380  }
  2381  
  2382  func (*removeRelationsSuite) TestAppNotThere(c *gc.C) {
  2383  	result := removeRelations(sampleRelations, "foo")
  2384  	c.Assert(result, jc.DeepEquals, sampleRelations)
  2385  }
  2386  
  2387  func (*removeRelationsSuite) TestAppBadRelationsKept(c *gc.C) {
  2388  	badRelations := [][]string{{"single value"}, {"three", "string", "values"}}
  2389  	result := removeRelations(badRelations, "foo")
  2390  	c.Assert(result, jc.DeepEquals, badRelations)
  2391  }
  2392  
  2393  func (*removeRelationsSuite) TestRemoveFromRight(c *gc.C) {
  2394  	result := removeRelations(sampleRelations, "etcd")
  2395  	c.Assert(result, jc.DeepEquals, [][]string{
  2396  		{"kubernetes-master:kube-control", "kubernetes-worker:kube-control"},
  2397  		{"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"},
  2398  		{"flannel:cni", "kubernetes-master:cni"},
  2399  		{"flannel:cni", "kubernetes-worker:cni"},
  2400  	})
  2401  }
  2402  
  2403  func (*removeRelationsSuite) TestRemoveFromLeft(c *gc.C) {
  2404  	result := removeRelations(sampleRelations, "flannel")
  2405  	c.Assert(result, jc.DeepEquals, [][]string{
  2406  		{"kubernetes-master:kube-control", "kubernetes-worker:kube-control"},
  2407  		{"kubernetes-master:etcd", "etcd:db"},
  2408  		{"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"},
  2409  	})
  2410  }
  2411  
  2412  func missingFileRegex(filename string) string {
  2413  	text := "no such file or directory"
  2414  	if runtime.GOOS == "windows" {
  2415  		text = "The system cannot find the file specified."
  2416  	}
  2417  	return fmt.Sprintf("open .*%s: %s", filename, text)
  2418  }