github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/charm.v6-unstable"
    17  	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
    18  
    19  	"github.com/juju/juju/api"
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/state/multiwatcher"
    23  	"github.com/juju/juju/state/watcher"
    24  	"github.com/juju/juju/testcharms"
    25  	coretesting "github.com/juju/juju/testing"
    26  )
    27  
    28  // LTS-dependent requires new entry upon new LTS release. There are numerous
    29  // places "xenial" exists in strings throughout this file. If we update the
    30  // target in testing/base.go:SetupSuite we'll need to also update the entries
    31  // herein.
    32  
    33  // runDeployCommand executes the deploy command in order to deploy the given
    34  // charm or bundle. The deployment output and error are returned.
    35  func runDeployCommand(c *gc.C, id string, args ...string) (string, error) {
    36  	args = append([]string{id}, args...)
    37  	ctx, err := coretesting.RunCommand(c, NewDefaultDeployCommand(), args...)
    38  	return strings.Trim(coretesting.Stderr(ctx), "\n"), err
    39  }
    40  
    41  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNotFoundCharmStore(c *gc.C) {
    42  	err := runDeploy(c, "bundle/no-such")
    43  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:bundle/no-such": bundle not found`)
    44  }
    45  
    46  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidFlags(c *gc.C) {
    47  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
    48  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
    49  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
    50  	_, err := runDeployCommand(c, "bundle/wordpress-simple", "--config", "config.yaml")
    51  	c.Assert(err, gc.ErrorMatches, "Flags provided but not supported when deploying a bundle: --config.")
    52  	_, err = runDeployCommand(c, "bundle/wordpress-simple", "-n", "2")
    53  	c.Assert(err, gc.ErrorMatches, "Flags provided but not supported when deploying a bundle: -n.")
    54  	_, err = runDeployCommand(c, "bundle/wordpress-simple", "--series", "xenial", "--force")
    55  	c.Assert(err, gc.ErrorMatches, "Flags provided but not supported when deploying a bundle: --force, --series.")
    56  }
    57  
    58  func (s *BundleDeployCharmStoreSuite) TestDeployBundleSuccess(c *gc.C) {
    59  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
    60  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
    61  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
    62  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
    65  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
    66  		"mysql":     {charm: "cs:xenial/mysql-42"},
    67  		"wordpress": {charm: "cs:xenial/wordpress-47"},
    68  	})
    69  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
    70  	s.assertUnitsCreated(c, map[string]string{
    71  		"mysql/0":     "0",
    72  		"wordpress/0": "1",
    73  	})
    74  }
    75  
    76  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWithTermsSuccess(c *gc.C) {
    77  	testcharms.UploadCharm(c, s.client, "xenial/terms1-17", "terms1")
    78  	testcharms.UploadCharm(c, s.client, "xenial/terms2-42", "terms2")
    79  	testcharms.UploadBundle(c, s.client, "bundle/terms-simple-1", "terms-simple")
    80  	_, err := runDeployCommand(c, "bundle/terms-simple")
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	s.assertCharmsUploaded(c, "cs:xenial/terms1-17", "cs:xenial/terms2-42")
    83  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
    84  		"terms1": {charm: "cs:xenial/terms1-17"},
    85  		"terms2": {charm: "cs:xenial/terms2-42"},
    86  	})
    87  	s.assertUnitsCreated(c, map[string]string{
    88  		"terms1/0": "0",
    89  		"terms2/0": "1",
    90  	})
    91  	c.Assert(s.termsString, gc.Not(gc.Equals), "")
    92  }
    93  
    94  func (s *BundleDeployCharmStoreSuite) TestDeployBundleStorage(c *gc.C) {
    95  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql-storage")
    96  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
    97  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-mysql-storage-1", "wordpress-with-mysql-storage")
    98  	_, err := runDeployCommand(
    99  		c, "bundle/wordpress-with-mysql-storage",
   100  		"--storage", "mysql:logs=tmpfs,10G", // override logs
   101  	)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   104  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   105  		"mysql": {
   106  			charm: "cs:xenial/mysql-42",
   107  			storage: map[string]state.StorageConstraints{
   108  				"data": state.StorageConstraints{Pool: "rootfs", Size: 50 * 1024, Count: 1},
   109  				"logs": state.StorageConstraints{Pool: "tmpfs", Size: 10 * 1024, Count: 1},
   110  			},
   111  		},
   112  		"wordpress": {charm: "cs:xenial/wordpress-47"},
   113  	})
   114  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   115  	s.assertUnitsCreated(c, map[string]string{
   116  		"mysql/0":     "0",
   117  		"wordpress/0": "1",
   118  	})
   119  }
   120  
   121  func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSpaceMissing(c *gc.C) {
   122  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   123  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings")
   124  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings")
   125  	output, err := runDeployCommand(c, "bundle/wordpress-with-endpoint-bindings")
   126  	c.Assert(err, gc.ErrorMatches, ""+
   127  		"cannot deploy bundle: cannot deploy application \"mysql\": "+
   128  		"cannot add application \"mysql\": unknown space \"db\" not valid")
   129  	c.Assert(output, gc.Equals, ""+
   130  		`Located bundle "cs:bundle/wordpress-with-endpoint-bindings-1"`+"\n"+
   131  		`Deploying charm "cs:xenial/mysql-42"`,
   132  	)
   133  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42")
   134  	s.assertApplicationsDeployed(c, map[string]serviceInfo{})
   135  	s.assertUnitsCreated(c, map[string]string{})
   136  }
   137  
   138  func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSuccess(c *gc.C) {
   139  	_, err := s.State.AddSpace("db", "", nil, false)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	_, err = s.State.AddSpace("public", "", nil, false)
   142  	c.Assert(err, jc.ErrorIsNil)
   143  
   144  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   145  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings")
   146  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings")
   147  	_, err = runDeployCommand(c, "bundle/wordpress-with-endpoint-bindings")
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-extra-bindings-47")
   150  
   151  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   152  		"mysql":                    {charm: "cs:xenial/mysql-42"},
   153  		"wordpress-extra-bindings": {charm: "cs:xenial/wordpress-extra-bindings-47"},
   154  	})
   155  	s.assertDeployedServiceBindings(c, map[string]serviceInfo{
   156  		"mysql": {
   157  			endpointBindings: map[string]string{"server": "db"},
   158  		},
   159  		"wordpress-extra-bindings": {
   160  			endpointBindings: map[string]string{
   161  				"cache":           "",
   162  				"url":             "public",
   163  				"logging-dir":     "",
   164  				"monitoring-port": "",
   165  				"db":              "db",
   166  				"cluster":         "",
   167  				"db-client":       "db",
   168  				"admin-api":       "public",
   169  				"foo-bar":         "",
   170  			},
   171  		},
   172  	})
   173  	s.assertRelationsEstablished(c, "wordpress-extra-bindings:cluster", "wordpress-extra-bindings:db mysql:server")
   174  	s.assertUnitsCreated(c, map[string]string{
   175  		"mysql/0":                    "0",
   176  		"wordpress-extra-bindings/0": "1",
   177  	})
   178  }
   179  
   180  func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwice(c *gc.C) {
   181  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   182  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   183  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   184  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	_, err = runDeployCommand(c, "bundle/wordpress-simple")
   187  	c.Assert(err, jc.ErrorIsNil)
   188  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   189  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   190  		"mysql":     {charm: "cs:xenial/mysql-42"},
   191  		"wordpress": {charm: "cs:xenial/wordpress-47"},
   192  	})
   193  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   194  	s.assertUnitsCreated(c, map[string]string{
   195  		"mysql/0":     "0",
   196  		"wordpress/0": "1",
   197  	})
   198  }
   199  
   200  func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharm(c *gc.C) {
   201  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   202  	url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   203  	s.changeReadPerm(c, url, clientUserName)
   204  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   205  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   208  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   209  		"mysql":     {charm: "cs:xenial/mysql-42"},
   210  		"wordpress": {charm: "cs:xenial/wordpress-47"},
   211  	})
   212  }
   213  
   214  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalPath(c *gc.C) {
   215  	dir := c.MkDir()
   216  	testcharms.Repo.ClonedDir(dir, "dummy")
   217  	path := filepath.Join(dir, "mybundle")
   218  	data := `
   219          series: xenial
   220          applications:
   221              dummy:
   222                  charm: ./dummy
   223                  series: xenial
   224                  num_units: 1
   225      `
   226  	err := ioutil.WriteFile(path, []byte(data), 0644)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	_, err = runDeployCommand(c, path)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	s.assertCharmsUploaded(c, "local:xenial/dummy-1")
   231  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   232  		"dummy": {charm: "local:xenial/dummy-1"},
   233  	})
   234  }
   235  
   236  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNoSeriesInCharmURL(c *gc.C) {
   237  	testcharms.UploadCharmMultiSeries(c, s.client, "~who/multi-series", "multi-series")
   238  	dir := c.MkDir()
   239  	testcharms.Repo.ClonedDir(dir, "dummy")
   240  	path := filepath.Join(dir, "mybundle")
   241  	data := `
   242          series: trusty
   243          applications:
   244              dummy:
   245                  charm: cs:~who/multi-series
   246      `
   247  	err := ioutil.WriteFile(path, []byte(data), 0644)
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	_, err = runDeployCommand(c, path)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	s.assertCharmsUploaded(c, "cs:~who/multi-series-0")
   252  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   253  		"dummy": {charm: "cs:~who/multi-series-0"},
   254  	})
   255  }
   256  
   257  func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharmUnauthorized(c *gc.C) {
   258  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   259  	url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   260  	s.changeReadPerm(c, url, "who")
   261  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   262  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
   263  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: .*: unauthorized: access denied for user "client-username"`)
   264  }
   265  
   266  type BundleDeployCharmStoreSuite struct {
   267  	charmStoreSuite
   268  }
   269  
   270  var _ = gc.Suite(&BundleDeployCharmStoreSuite{})
   271  
   272  func (s *BundleDeployCharmStoreSuite) SetUpSuite(c *gc.C) {
   273  	s.charmStoreSuite.SetUpSuite(c)
   274  	s.PatchValue(&watcher.Period, 10*time.Millisecond)
   275  }
   276  
   277  func (s *BundleDeployCharmStoreSuite) Client() *csclient.Client {
   278  	return s.client
   279  }
   280  
   281  // DeployBundleYAML uses the given bundle content to create a bundle in the
   282  // local repository and then deploy it. It returns the bundle deployment output
   283  // and error.
   284  func (s *BundleDeployCharmStoreSuite) DeployBundleYAML(c *gc.C, content string) (string, error) {
   285  	bundlePath := filepath.Join(c.MkDir(), "example")
   286  	c.Assert(os.Mkdir(bundlePath, 0777), jc.ErrorIsNil)
   287  	defer os.RemoveAll(bundlePath)
   288  	err := ioutil.WriteFile(filepath.Join(bundlePath, "bundle.yaml"), []byte(content), 0644)
   289  	c.Assert(err, jc.ErrorIsNil)
   290  	err = ioutil.WriteFile(filepath.Join(bundlePath, "README.md"), []byte("README"), 0644)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	return runDeployCommand(c, bundlePath)
   293  }
   294  
   295  var deployBundleErrorsTests = []struct {
   296  	about   string
   297  	content string
   298  	err     string
   299  }{{
   300  	about: "local charm not found",
   301  	content: `
   302          applications:
   303              mysql:
   304                  charm: ./mysql
   305                  num_units: 1
   306      `,
   307  	err: `the provided bundle has the following errors:
   308  charm path in application "mysql" does not exist: mysql`,
   309  }, {
   310  	about: "charm store charm not found",
   311  	content: `
   312          applications:
   313              rails:
   314                  charm: xenial/rails-42
   315                  num_units: 1
   316      `,
   317  	err: `cannot deploy bundle: cannot resolve URL "xenial/rails-42": cannot resolve URL "cs:xenial/rails-42": charm not found`,
   318  }, {
   319  	about:   "invalid bundle content",
   320  	content: "!",
   321  	err:     `cannot unmarshal bundle data: yaml: .*`,
   322  }, {
   323  	about: "invalid bundle data",
   324  	content: `
   325          applications:
   326              mysql:
   327                  charm: mysql
   328                  num_units: -1
   329      `,
   330  	err: `the provided bundle has the following errors:
   331  negative number of units specified on application "mysql"`,
   332  }, {
   333  	about: "invalid constraints",
   334  	content: `
   335          applications:
   336              mysql:
   337                  charm: mysql
   338                  num_units: 1
   339                  constraints: bad-wolf
   340      `,
   341  	err: `the provided bundle has the following errors:
   342  invalid constraints "bad-wolf" in application "mysql": malformed constraint "bad-wolf"`,
   343  }, {
   344  	about: "multiple bundle verification errors",
   345  	content: `
   346          applications:
   347              mysql:
   348                  charm: mysql
   349                  num_units: -1
   350                  constraints: bad-wolf
   351      `,
   352  	err: `the provided bundle has the following errors:
   353  invalid constraints "bad-wolf" in application "mysql": malformed constraint "bad-wolf"
   354  negative number of units specified on application "mysql"`,
   355  }, {
   356  	about: "bundle inception",
   357  	content: `
   358          applications:
   359              example:
   360                  charm: local:wordpress
   361                  num_units: 1
   362      `,
   363  	err: `cannot deploy bundle: cannot resolve URL "local:wordpress": unknown schema for charm URL "local:wordpress"`,
   364  }}
   365  
   366  func (s *BundleDeployCharmStoreSuite) TestDeployBundleErrors(c *gc.C) {
   367  	for i, test := range deployBundleErrorsTests {
   368  		c.Logf("test %d: %s", i, test.about)
   369  		_, err := s.DeployBundleYAML(c, test.content)
   370  		c.Check(err, gc.ErrorMatches, test.err)
   371  	}
   372  }
   373  
   374  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidOptions(c *gc.C) {
   375  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   376  	_, err := s.DeployBundleYAML(c, `
   377          applications:
   378              wp:
   379                  charm: xenial/wordpress-42
   380                  num_units: 1
   381                  options:
   382                      blog-title: 42
   383      `)
   384  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": option "blog-title" expected string, got 42`)
   385  }
   386  
   387  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidMachineContainerType(c *gc.C) {
   388  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   389  	_, err := s.DeployBundleYAML(c, `
   390          applications:
   391              wp:
   392                  charm: xenial/wordpress
   393                  num_units: 1
   394                  to: ["bad:1"]
   395          machines:
   396              1:
   397      `)
   398  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot create machine for holding wp unit: invalid container type "bad"`)
   399  }
   400  
   401  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidSeries(c *gc.C) {
   402  	testcharms.UploadCharm(c, s.client, "vivid/django-0", "dummy")
   403  	_, err := s.DeployBundleYAML(c, `
   404          applications:
   405              django:
   406                  charm: vivid/django
   407                  num_units: 1
   408                  to:
   409                      - 1
   410          machines:
   411              1:
   412                  series: xenial
   413      `)
   414  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot add unit for application "django": adding new machine to host unit "django/0": cannot assign unit "django/0" to machine 0: series does not match`)
   415  }
   416  
   417  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWatcherTimeout(c *gc.C) {
   418  	// Inject an "AllWatcher" that never delivers a result.
   419  	ch := make(chan struct{})
   420  	defer close(ch)
   421  	watcher := mockAllWatcher{
   422  		next: func() []multiwatcher.Delta {
   423  			<-ch
   424  			return nil
   425  		},
   426  	}
   427  	s.PatchValue(&watchAll, func(*api.Client) (allWatcher, error) {
   428  		return watcher, nil
   429  	})
   430  
   431  	testcharms.UploadCharm(c, s.client, "xenial/django-0", "dummy")
   432  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   433  	s.PatchValue(&updateUnitStatusPeriod, 0*time.Second)
   434  	_, err := s.DeployBundleYAML(c, `
   435          applications:
   436              django:
   437                  charm: django
   438                  num_units: 1
   439              wordpress:
   440                  charm: wordpress
   441                  num_units: 1
   442                  to: [django]
   443      `)
   444  	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`)
   445  }
   446  
   447  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeployment(c *gc.C) {
   448  	charmsPath := c.MkDir()
   449  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   450  	wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress")
   451  	_, err := s.DeployBundleYAML(c, fmt.Sprintf(`
   452          series: xenial
   453          applications:
   454              wordpress:
   455                  charm: %s
   456                  num_units: 1
   457              mysql:
   458                  charm: %s
   459                  num_units: 2
   460          relations:
   461              - ["wordpress:db", "mysql:server"]
   462      `, wordpressPath, mysqlPath))
   463  	c.Assert(err, jc.ErrorIsNil)
   464  	s.assertCharmsUploaded(c, "local:xenial/mysql-1", "local:xenial/wordpress-3")
   465  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   466  		"mysql":     {charm: "local:xenial/mysql-1"},
   467  		"wordpress": {charm: "local:xenial/wordpress-3"},
   468  	})
   469  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   470  	s.assertUnitsCreated(c, map[string]string{
   471  		"mysql/0":     "0",
   472  		"mysql/1":     "1",
   473  		"wordpress/0": "2",
   474  	})
   475  }
   476  
   477  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalAndCharmStoreCharms(c *gc.C) {
   478  	charmsPath := c.MkDir()
   479  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   480  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   481  	_, err := s.DeployBundleYAML(c, fmt.Sprintf(`
   482          series: xenial
   483          applications:
   484              wordpress:
   485                  charm: xenial/wordpress-42
   486                  series: xenial
   487                  num_units: 1
   488              mysql:
   489                  charm: %s
   490                  num_units: 1
   491          relations:
   492              - ["wordpress:db", "mysql:server"]
   493      `, mysqlPath))
   494  	c.Assert(err, jc.ErrorIsNil)
   495  	s.assertCharmsUploaded(c, "local:xenial/mysql-1", "cs:xenial/wordpress-42")
   496  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   497  		"mysql":     {charm: "local:xenial/mysql-1"},
   498  		"wordpress": {charm: "cs:xenial/wordpress-42"},
   499  	})
   500  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   501  	s.assertUnitsCreated(c, map[string]string{
   502  		"mysql/0":     "0",
   503  		"wordpress/0": "1",
   504  	})
   505  }
   506  
   507  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationOptions(c *gc.C) {
   508  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   509  	testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy")
   510  	_, err := s.DeployBundleYAML(c, `
   511          applications:
   512              wordpress:
   513                  charm: wordpress
   514                  num_units: 1
   515                  options:
   516                      blog-title: these are the voyages
   517              customized:
   518                  charm: precise/dummy-0
   519                  num_units: 1
   520                  options:
   521                      username: who
   522                      skill-level: 47
   523      `)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  	s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42")
   526  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   527  		"customized": {
   528  			charm:  "cs:precise/dummy-0",
   529  			config: charm.Settings{"username": "who", "skill-level": int64(47)},
   530  		},
   531  		"wordpress": {
   532  			charm:  "cs:xenial/wordpress-42",
   533  			config: charm.Settings{"blog-title": "these are the voyages"},
   534  		},
   535  	})
   536  	s.assertUnitsCreated(c, map[string]string{
   537  		"wordpress/0":  "1",
   538  		"customized/0": "0",
   539  	})
   540  }
   541  
   542  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationConstrants(c *gc.C) {
   543  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   544  	testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy")
   545  	_, err := s.DeployBundleYAML(c, `
   546          applications:
   547              wordpress:
   548                  charm: wordpress
   549                  constraints: mem=4G cores=2
   550              customized:
   551                  charm: precise/dummy-0
   552                  num_units: 1
   553                  constraints: arch=i386
   554      `)
   555  	c.Assert(err, jc.ErrorIsNil)
   556  	s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42")
   557  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   558  		"customized": {
   559  			charm:       "cs:precise/dummy-0",
   560  			constraints: constraints.MustParse("arch=i386"),
   561  		},
   562  		"wordpress": {
   563  			charm:       "cs:xenial/wordpress-42",
   564  			constraints: constraints.MustParse("mem=4G cores=2"),
   565  		},
   566  	})
   567  	s.assertUnitsCreated(c, map[string]string{
   568  		"customized/0": "0",
   569  	})
   570  }
   571  
   572  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationUpgrade(c *gc.C) {
   573  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   574  	testcharms.UploadCharm(c, s.client, "vivid/upgrade-1", "upgrade1")
   575  	testcharms.UploadCharm(c, s.client, "vivid/upgrade-2", "upgrade2")
   576  
   577  	// First deploy the bundle.
   578  	_, err := s.DeployBundleYAML(c, `
   579          applications:
   580              wordpress:
   581                  charm: wordpress
   582                  num_units: 1
   583                  options:
   584                      blog-title: these are the voyages
   585                  constraints: spaces=final,frontiers mem=8000M
   586              up:
   587                  charm: vivid/upgrade-1
   588                  num_units: 1
   589      `)
   590  	c.Assert(err, jc.ErrorIsNil)
   591  	s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:xenial/wordpress-42")
   592  
   593  	// Then deploy a new bundle with modified charm revision and options.
   594  	_, err = s.DeployBundleYAML(c, `
   595          applications:
   596              wordpress:
   597                  charm: wordpress
   598                  num_units: 1
   599                  options:
   600                      blog-title: new title
   601                  constraints: spaces=new cores=8
   602              up:
   603                  charm: vivid/upgrade-2
   604                  num_units: 1
   605      `)
   606  	c.Assert(err, jc.ErrorIsNil)
   607  	s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:vivid/upgrade-2", "cs:xenial/wordpress-42")
   608  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   609  		"up": {charm: "cs:vivid/upgrade-2"},
   610  		"wordpress": {
   611  			charm:       "cs:xenial/wordpress-42",
   612  			config:      charm.Settings{"blog-title": "new title"},
   613  			constraints: constraints.MustParse("spaces=new cores=8"),
   614  		},
   615  	})
   616  	s.assertUnitsCreated(c, map[string]string{
   617  		"up/0":        "0",
   618  		"wordpress/0": "1",
   619  	})
   620  }
   621  
   622  func (s *BundleDeployCharmStoreSuite) TestDeployBundleExpose(c *gc.C) {
   623  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   624  	content := `
   625          applications:
   626              wordpress:
   627                  charm: wordpress
   628                  num_units: 1
   629                  expose: true
   630      `
   631  	expectedApplications := map[string]serviceInfo{
   632  		"wordpress": {
   633  			charm:   "cs:xenial/wordpress-42",
   634  			exposed: true,
   635  		},
   636  	}
   637  
   638  	// First deploy the bundle.
   639  	_, err := s.DeployBundleYAML(c, content)
   640  	c.Assert(err, jc.ErrorIsNil)
   641  	s.assertApplicationsDeployed(c, expectedApplications)
   642  
   643  	// Then deploy the same bundle again: no error is produced when the application
   644  	// is exposed again.
   645  	_, err = s.DeployBundleYAML(c, content)
   646  	c.Assert(err, jc.ErrorIsNil)
   647  	s.assertApplicationsDeployed(c, expectedApplications)
   648  
   649  	// Then deploy a bundle with the application unexposed, and check that the
   650  	// application is not unexposed.
   651  	_, err = s.DeployBundleYAML(c, `
   652          applications:
   653              wordpress:
   654                  charm: wordpress
   655                  num_units: 1
   656                  expose: false
   657      `)
   658  	c.Assert(err, jc.ErrorIsNil)
   659  	s.assertApplicationsDeployed(c, expectedApplications)
   660  }
   661  
   662  func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationUpgradeFailure(c *gc.C) {
   663  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   664  
   665  	// Try upgrading to a different charm name.
   666  	testcharms.UploadCharm(c, s.client, "xenial/incompatible-42", "wordpress")
   667  	_, err := s.DeployBundleYAML(c, `
   668          applications:
   669              wordpress:
   670                  charm: xenial/incompatible-42
   671                  num_units: 1
   672      `)
   673  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade application "wordpress": bundle charm "cs:xenial/incompatible-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
   674  
   675  	// Try upgrading to a different series.
   676  	// Note that this test comes before the next one because
   677  	// otherwise we can't resolve the charm URL because the charm's
   678  	// "base entity" is not marked as promulgated so the query by
   679  	// promulgated will find it.
   680  	testcharms.UploadCharm(c, s.client, "vivid/wordpress-42", "wordpress")
   681  	_, err = s.DeployBundleYAML(c, `
   682          applications:
   683              wordpress:
   684                  charm: vivid/wordpress
   685                  num_units: 1
   686      `)
   687  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade application "wordpress": bundle charm "cs:vivid/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
   688  
   689  	// Try upgrading to a different user.
   690  	testcharms.UploadCharm(c, s.client, "~who/xenial/wordpress-42", "wordpress")
   691  	_, err = s.DeployBundleYAML(c, `
   692          applications:
   693              wordpress:
   694                  charm: cs:~who/xenial/wordpress-42
   695                  num_units: 1
   696      `)
   697  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade application "wordpress": bundle charm "cs:~who/xenial/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
   698  }
   699  
   700  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMultipleRelations(c *gc.C) {
   701  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   702  	testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql")
   703  	testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql")
   704  	testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish")
   705  	_, err := s.DeployBundleYAML(c, `
   706          applications:
   707              wp:
   708                  charm: wordpress
   709                  num_units: 1
   710              mysql:
   711                  charm: mysql
   712                  num_units: 1
   713              pgres:
   714                  charm: xenial/postgres-2
   715                  num_units: 1
   716              varnish:
   717                  charm: xenial/varnish
   718                  num_units: 1
   719          relations:
   720              - ["wp:db", "mysql:server"]
   721              - ["wp:db", "pgres:server"]
   722              - ["varnish:webcache", "wp:cache"]
   723      `)
   724  	c.Assert(err, jc.ErrorIsNil)
   725  	s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:db pgres:server", "wp:cache varnish:webcache")
   726  	s.assertUnitsCreated(c, map[string]string{
   727  		"mysql/0":   "0",
   728  		"pgres/0":   "1",
   729  		"varnish/0": "2",
   730  		"wp/0":      "3",
   731  	})
   732  }
   733  
   734  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNewRelations(c *gc.C) {
   735  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   736  	testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql")
   737  	testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql")
   738  	testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish")
   739  	_, err := s.DeployBundleYAML(c, `
   740          applications:
   741              wp:
   742                  charm: wordpress
   743                  num_units: 1
   744              mysql:
   745                  charm: mysql
   746                  num_units: 1
   747              varnish:
   748                  charm: xenial/varnish
   749                  num_units: 1
   750          relations:
   751              - ["wp:db", "mysql:server"]
   752      `)
   753  	c.Assert(err, jc.ErrorIsNil)
   754  	_, err = s.DeployBundleYAML(c, `
   755          applications:
   756              wp:
   757                  charm: wordpress
   758                  num_units: 1
   759              mysql:
   760                  charm: mysql
   761                  num_units: 1
   762              varnish:
   763                  charm: xenial/varnish
   764                  num_units: 1
   765          relations:
   766              - ["wp:db", "mysql:server"]
   767              - ["varnish:webcache", "wp:cache"]
   768      `)
   769  	c.Assert(err, jc.ErrorIsNil)
   770  	s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:cache varnish:webcache")
   771  	s.assertUnitsCreated(c, map[string]string{
   772  		"mysql/0":   "0",
   773  		"varnish/0": "1",
   774  		"wp/0":      "2",
   775  	})
   776  }
   777  
   778  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachinesUnitsPlacement(c *gc.C) {
   779  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   780  	testcharms.UploadCharm(c, s.client, "xenial/mysql-2", "mysql")
   781  
   782  	content := `
   783          applications:
   784              wp:
   785                  charm: cs:xenial/wordpress-0
   786                  num_units: 2
   787                  to:
   788                      - 1
   789                      - lxd:2
   790                  options:
   791                      blog-title: these are the voyages
   792              sql:
   793                  charm: cs:xenial/mysql
   794                  num_units: 2
   795                  to:
   796                      - lxd:wp/0
   797                      - new
   798          machines:
   799              1:
   800                  series: xenial
   801              2:
   802      `
   803  	_, err := s.DeployBundleYAML(c, content)
   804  	c.Assert(err, jc.ErrorIsNil)
   805  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   806  		"sql": {charm: "cs:xenial/mysql-2"},
   807  		"wp": {
   808  			charm:  "cs:xenial/wordpress-0",
   809  			config: charm.Settings{"blog-title": "these are the voyages"},
   810  		},
   811  	})
   812  	s.assertRelationsEstablished(c)
   813  
   814  	// We explicitly pull out the map creation in the call to
   815  	// s.assertUnitsCreated() and create the map as a new variable
   816  	// because this /appears/ to tickle a bug on ppc64le using
   817  	// gccgo-4.9; the bug is that the map on the receiving side
   818  	// does not have the same contents as it does here - which is
   819  	// weird because that pattern is used elsewhere in this
   820  	// function. And just pulling the map instantiation out of the
   821  	// call is not enough; we need to do something benign with the
   822  	// variable to keep a reference beyond the call to the
   823  	// s.assertUnitsCreated(). I have to chosen to delete a
   824  	// non-existent key. This problem does not occur on amd64
   825  	// using gc or gccgo-4.9. Nor does it happen using go1.6 on
   826  	// ppc64. Once we switch to go1.6 across the board this change
   827  	// should be reverted. See http://pad.lv/1556116.
   828  	expectedUnits := map[string]string{
   829  		"sql/0": "0/lxd/0",
   830  		"sql/1": "2",
   831  		"wp/0":  "0",
   832  		"wp/1":  "1/lxd/0",
   833  	}
   834  	s.assertUnitsCreated(c, expectedUnits)
   835  	delete(expectedUnits, "non-existent")
   836  
   837  	// Redeploy the same bundle again.
   838  	_, err = s.DeployBundleYAML(c, content)
   839  	c.Assert(err, jc.ErrorIsNil)
   840  	s.assertUnitsCreated(c, map[string]string{
   841  		"sql/0": "0/lxd/0",
   842  		"sql/1": "2",
   843  		"wp/0":  "0",
   844  		"wp/1":  "1/lxd/0",
   845  	})
   846  }
   847  
   848  func (s *BundleDeployCharmStoreSuite) TestLXCTreatedAsLXD(c *gc.C) {
   849  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   850  
   851  	// Note that we use lxc here, to represent a 1.x bundle that specifies lxc.
   852  	content := `
   853          applications:
   854              wp:
   855                  charm: cs:xenial/wordpress-0
   856                  num_units: 1
   857                  to:
   858                      - lxc:0
   859                  options:
   860                      blog-title: these are the voyages
   861              wp2:
   862                  charm: cs:xenial/wordpress-0
   863                  num_units: 1
   864                  to:
   865                      - lxc:0
   866                  options:
   867                      blog-title: these are the voyages
   868          machines:
   869              0:
   870                  series: xenial
   871      `
   872  	output, err := s.DeployBundleYAML(c, content)
   873  	c.Assert(err, jc.ErrorIsNil)
   874  	expectedUnits := map[string]string{
   875  		"wp/0":  "0/lxd/0",
   876  		"wp2/0": "0/lxd/1",
   877  	}
   878  	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.")
   879  	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.")
   880  	// The message exists.
   881  	c.Assert(idx, jc.GreaterThan, -1)
   882  	// No more than one instance of the message was printed.
   883  	c.Assert(idx, gc.Equals, lastIdx)
   884  	s.assertUnitsCreated(c, expectedUnits)
   885  }
   886  
   887  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachineAttributes(c *gc.C) {
   888  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
   889  	_, err := s.DeployBundleYAML(c, `
   890          applications:
   891              django:
   892                  charm: cs:xenial/django-42
   893                  num_units: 2
   894                  to:
   895                      - 1
   896                      - new
   897          machines:
   898              1:
   899                  series: xenial
   900                  constraints: "cores=4 mem=4G"
   901                  annotations:
   902                      foo: bar
   903      `)
   904  	c.Assert(err, jc.ErrorIsNil)
   905  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   906  		"django": {charm: "cs:xenial/django-42"},
   907  	})
   908  	s.assertRelationsEstablished(c)
   909  	s.assertUnitsCreated(c, map[string]string{
   910  		"django/0": "0",
   911  		"django/1": "1",
   912  	})
   913  	m, err := s.State.Machine("0")
   914  	c.Assert(err, jc.ErrorIsNil)
   915  	c.Assert(m.Series(), gc.Equals, "xenial")
   916  	cons, err := m.Constraints()
   917  	c.Assert(err, jc.ErrorIsNil)
   918  	expectedCons, err := constraints.Parse("cores=4 mem=4G")
   919  	c.Assert(err, jc.ErrorIsNil)
   920  	c.Assert(cons, jc.DeepEquals, expectedCons)
   921  	ann, err := s.State.Annotations(m)
   922  	c.Assert(err, jc.ErrorIsNil)
   923  	c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"})
   924  }
   925  
   926  func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwiceScaleUp(c *gc.C) {
   927  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
   928  	_, err := s.DeployBundleYAML(c, `
   929          applications:
   930              django:
   931                  charm: cs:xenial/django-42
   932                  num_units: 2
   933      `)
   934  	c.Assert(err, jc.ErrorIsNil)
   935  	_, err = s.DeployBundleYAML(c, `
   936          applications:
   937              django:
   938                  charm: cs:xenial/django-42
   939                  num_units: 5
   940      `)
   941  	c.Assert(err, jc.ErrorIsNil)
   942  	s.assertUnitsCreated(c, map[string]string{
   943  		"django/0": "0",
   944  		"django/1": "1",
   945  		"django/2": "2",
   946  		"django/3": "3",
   947  		"django/4": "4",
   948  	})
   949  }
   950  
   951  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedInApplication(c *gc.C) {
   952  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
   953  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   954  	_, err := s.DeployBundleYAML(c, `
   955          applications:
   956              wordpress:
   957                  charm: wordpress
   958                  num_units: 3
   959              django:
   960                  charm: cs:xenial/django-42
   961                  num_units: 2
   962                  to: [wordpress]
   963      `)
   964  	c.Assert(err, jc.ErrorIsNil)
   965  	s.assertUnitsCreated(c, map[string]string{
   966  		"django/0":    "0",
   967  		"django/1":    "1",
   968  		"wordpress/0": "0",
   969  		"wordpress/1": "1",
   970  		"wordpress/2": "2",
   971  	})
   972  }
   973  
   974  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitColocationWithUnit(c *gc.C) {
   975  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
   976  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
   977  	testcharms.UploadCharm(c, s.client, "xenial/rails-0", "dummy")
   978  	_, err := s.DeployBundleYAML(c, `
   979          applications:
   980              memcached:
   981                  charm: cs:xenial/mem-47
   982                  num_units: 3
   983                  to: [1, new]
   984              django:
   985                  charm: cs:xenial/django-42
   986                  num_units: 5
   987                  to:
   988                      - memcached/0
   989                      - lxd:memcached/1
   990                      - lxd:memcached/2
   991                      - kvm:ror
   992              ror:
   993                  charm: rails
   994                  num_units: 2
   995                  to:
   996                      - new
   997                      - 1
   998          machines:
   999              1:
  1000                  series: xenial
  1001      `)
  1002  	c.Assert(err, jc.ErrorIsNil)
  1003  	s.assertUnitsCreated(c, map[string]string{
  1004  		"django/0":    "0",
  1005  		"django/1":    "0/kvm/0",
  1006  		"django/2":    "1/lxd/0",
  1007  		"django/3":    "2/lxd/0",
  1008  		"django/4":    "3/kvm/0",
  1009  		"memcached/0": "0",
  1010  		"memcached/1": "1",
  1011  		"memcached/2": "2",
  1012  		"ror/0":       "0",
  1013  		"ror/1":       "3",
  1014  	})
  1015  }
  1016  
  1017  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedToMachines(c *gc.C) {
  1018  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1019  	_, err := s.DeployBundleYAML(c, `
  1020          applications:
  1021              django:
  1022                  charm: cs:django
  1023                  num_units: 7
  1024                  to:
  1025                      - new
  1026                      - 4
  1027                      - kvm:8
  1028                      - lxd:4
  1029                      - lxd:4
  1030                      - lxd:new
  1031          machines:
  1032              4:
  1033              8:
  1034      `)
  1035  	c.Assert(err, jc.ErrorIsNil)
  1036  	s.assertUnitsCreated(c, map[string]string{
  1037  		"django/0": "0",       // Machine "4" in the bundle.
  1038  		"django/1": "2",       // Machine "new" in the bundle.
  1039  		"django/2": "1/kvm/0", // The KVM container in bundle machine "8".
  1040  		"django/3": "0/lxd/0", // First lxd container in bundle machine "4".
  1041  		"django/4": "0/lxd/1", // Second lxd container in bundle machine "4".
  1042  		"django/5": "3/lxd/0", // First lxd in new machine.
  1043  		"django/6": "4/lxd/0", // Second lxd in new machine.
  1044  	})
  1045  }
  1046  
  1047  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMassiveUnitColocation(c *gc.C) {
  1048  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1049  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1050  	testcharms.UploadCharm(c, s.client, "xenial/rails-0", "dummy")
  1051  	_, err := s.DeployBundleYAML(c, `
  1052          applications:
  1053              memcached:
  1054                  charm: cs:xenial/mem-47
  1055                  num_units: 3
  1056                  to: [1, 2, 3]
  1057              django:
  1058                  charm: cs:xenial/django-42
  1059                  num_units: 4
  1060                  to:
  1061                      - 1
  1062                      - lxd:memcached
  1063              ror:
  1064                  charm: rails
  1065                  num_units: 3
  1066                  to:
  1067                      - 1
  1068                      - kvm:3
  1069          machines:
  1070              1:
  1071              2:
  1072              3:
  1073      `)
  1074  	c.Assert(err, jc.ErrorIsNil)
  1075  	s.assertUnitsCreated(c, map[string]string{
  1076  		"django/0":    "0",
  1077  		"django/1":    "0/lxd/0",
  1078  		"django/2":    "1/lxd/0",
  1079  		"django/3":    "2/lxd/0",
  1080  		"memcached/0": "0",
  1081  		"memcached/1": "1",
  1082  		"memcached/2": "2",
  1083  		"ror/0":       "0",
  1084  		"ror/1":       "2/kvm/0",
  1085  		"ror/2":       "2/kvm/1",
  1086  	})
  1087  
  1088  	// Redeploy a very similar bundle with another application unit. The new unit
  1089  	// is placed on machine 1 because that's the least crowded machine.
  1090  	content := `
  1091          applications:
  1092              memcached:
  1093                  charm: cs:xenial/mem-47
  1094                  num_units: 3
  1095                  to: [1, 2, 3]
  1096              django:
  1097                  charm: cs:xenial/django-42
  1098                  num_units: 4
  1099                  to:
  1100                      - 1
  1101                      - lxd:memcached
  1102              node:
  1103                  charm: cs:xenial/django-42
  1104                  num_units: 1
  1105                  to:
  1106                      - lxd:memcached
  1107          machines:
  1108              1:
  1109              2:
  1110              3:
  1111      `
  1112  	_, err = s.DeployBundleYAML(c, content)
  1113  	c.Assert(err, jc.ErrorIsNil)
  1114  
  1115  	// Redeploy the same bundle again and check that nothing happens.
  1116  	_, err = s.DeployBundleYAML(c, content)
  1117  	c.Assert(err, jc.ErrorIsNil)
  1118  	s.assertUnitsCreated(c, map[string]string{
  1119  		"django/0":    "0",
  1120  		"django/1":    "0/lxd/0",
  1121  		"django/2":    "1/lxd/0",
  1122  		"django/3":    "2/lxd/0",
  1123  		"memcached/0": "0",
  1124  		"memcached/1": "1",
  1125  		"memcached/2": "2",
  1126  		"node/0":      "1/lxd/1",
  1127  		"ror/0":       "0",
  1128  		"ror/1":       "2/kvm/0",
  1129  		"ror/2":       "2/kvm/1",
  1130  	})
  1131  }
  1132  
  1133  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWithAnnotations_OutputIsCorrect(c *gc.C) {
  1134  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1135  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1136  	output, err := s.DeployBundleYAML(c, `
  1137          applications:
  1138              django:
  1139                  charm: cs:django
  1140                  num_units: 1
  1141                  annotations:
  1142                      key1: value1
  1143                      key2: value2
  1144                  to: [1]
  1145              memcached:
  1146                  charm: xenial/mem-47
  1147                  num_units: 1
  1148          machines:
  1149              1:
  1150                  annotations: {foo: bar}
  1151      `)
  1152  	c.Assert(err, jc.ErrorIsNil)
  1153  
  1154  	c.Check(output, gc.Equals, ""+
  1155  		`Deploying charm "cs:xenial/django-42"`+"\n"+
  1156  		`Deploying charm "cs:xenial/mem-47"`+"\n"+
  1157  		`Deploy of bundle completed.`,
  1158  	)
  1159  }
  1160  
  1161  func (s *BundleDeployCharmStoreSuite) TestDeployBundleAnnotations(c *gc.C) {
  1162  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1163  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1164  	_, err := s.DeployBundleYAML(c, `
  1165          applications:
  1166              django:
  1167                  charm: cs:django
  1168                  num_units: 1
  1169                  annotations:
  1170                      key1: value1
  1171                      key2: value2
  1172                  to: [1]
  1173              memcached:
  1174                  charm: xenial/mem-47
  1175                  num_units: 1
  1176          machines:
  1177              1:
  1178                  annotations: {foo: bar}
  1179      `)
  1180  	c.Assert(err, jc.ErrorIsNil)
  1181  	svc, err := s.State.Application("django")
  1182  	c.Assert(err, jc.ErrorIsNil)
  1183  	ann, err := s.State.Annotations(svc)
  1184  	c.Assert(err, jc.ErrorIsNil)
  1185  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1186  		"key1": "value1",
  1187  		"key2": "value2",
  1188  	})
  1189  	m, err := s.State.Machine("0")
  1190  	c.Assert(err, jc.ErrorIsNil)
  1191  	ann, err = s.State.Annotations(m)
  1192  	c.Assert(err, jc.ErrorIsNil)
  1193  	c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"})
  1194  
  1195  	// Update the annotations and deploy the bundle again.
  1196  	_, err = s.DeployBundleYAML(c, `
  1197          applications:
  1198              django:
  1199                  charm: cs:django
  1200                  num_units: 1
  1201                  annotations:
  1202                      key1: new value!
  1203                      key2: value2
  1204                  to: [1]
  1205          machines:
  1206              1:
  1207                  annotations: {answer: 42}
  1208      `)
  1209  	c.Assert(err, jc.ErrorIsNil)
  1210  	ann, err = s.State.Annotations(svc)
  1211  	c.Assert(err, jc.ErrorIsNil)
  1212  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1213  		"key1": "new value!",
  1214  		"key2": "value2",
  1215  	})
  1216  	ann, err = s.State.Annotations(m)
  1217  	c.Assert(err, jc.ErrorIsNil)
  1218  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1219  		"foo":    "bar",
  1220  		"answer": "42",
  1221  	})
  1222  }
  1223  
  1224  type mockAllWatcher struct {
  1225  	next func() []multiwatcher.Delta
  1226  }
  1227  
  1228  func (w mockAllWatcher) Next() ([]multiwatcher.Delta, error) {
  1229  	return w.next(), nil
  1230  }
  1231  
  1232  func (mockAllWatcher) Stop() error {
  1233  	return nil
  1234  }