github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/service/bundle_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     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, NewDeployCommand(), 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  	output, err := runDeployCommand(c, "bundle/wordpress-simple")
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	expectedOutput := `
    65  added charm cs:xenial/mysql-42
    66  service mysql deployed (charm cs:xenial/mysql-42 with the charm series "xenial")
    67  added charm cs:xenial/wordpress-47
    68  service wordpress deployed (charm cs:xenial/wordpress-47 with the charm series "xenial")
    69  related wordpress:db and mysql:server
    70  added mysql/0 unit to new machine
    71  added wordpress/0 unit to new machine
    72  deployment of bundle "cs:bundle/wordpress-simple-1" completed`
    73  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
    74  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
    75  	s.assertServicesDeployed(c, map[string]serviceInfo{
    76  		"mysql":     {charm: "cs:xenial/mysql-42"},
    77  		"wordpress": {charm: "cs:xenial/wordpress-47"},
    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) TestDeployBundleWithTermsSuccess(c *gc.C) {
    87  	testcharms.UploadCharm(c, s.client, "xenial/terms1-17", "terms1")
    88  	testcharms.UploadCharm(c, s.client, "xenial/terms2-42", "terms2")
    89  	testcharms.UploadBundle(c, s.client, "bundle/terms-simple-1", "terms-simple")
    90  	output, err := runDeployCommand(c, "bundle/terms-simple")
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	expectedOutput := `
    93  added charm cs:xenial/terms1-17
    94  service terms1 deployed (charm cs:xenial/terms1-17 with the charm series "xenial")
    95  added charm cs:xenial/terms2-42
    96  service terms2 deployed (charm cs:xenial/terms2-42 with the charm series "xenial")
    97  added terms1/0 unit to new machine
    98  added terms2/0 unit to new machine
    99  deployment of bundle "cs:bundle/terms-simple-1" completed`
   100  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   101  	s.assertCharmsUploaded(c, "cs:xenial/terms1-17", "cs:xenial/terms2-42")
   102  	s.assertServicesDeployed(c, map[string]serviceInfo{
   103  		"terms1": {charm: "cs:xenial/terms1-17"},
   104  		"terms2": {charm: "cs:xenial/terms2-42"},
   105  	})
   106  	s.assertUnitsCreated(c, map[string]string{
   107  		"terms1/0": "0",
   108  		"terms2/0": "1",
   109  	})
   110  	c.Assert(s.termsString, gc.Not(gc.Equals), "")
   111  }
   112  
   113  func (s *BundleDeployCharmStoreSuite) TestDeployBundleStorage(c *gc.C) {
   114  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql-storage")
   115  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   116  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-mysql-storage-1", "wordpress-with-mysql-storage")
   117  	output, err := runDeployCommand(
   118  		c, "bundle/wordpress-with-mysql-storage",
   119  		"--storage", "mysql:logs=tmpfs,10G", // override logs
   120  	)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	expectedOutput := `
   123  added charm cs:xenial/mysql-42
   124  service mysql deployed (charm cs:xenial/mysql-42 with the charm series "xenial")
   125  added charm cs:xenial/wordpress-47
   126  service wordpress deployed (charm cs:xenial/wordpress-47 with the charm series "xenial")
   127  related wordpress:db and mysql:server
   128  added mysql/0 unit to new machine
   129  added wordpress/0 unit to new machine
   130  deployment of bundle "cs:bundle/wordpress-with-mysql-storage-1" completed`
   131  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   132  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   133  	s.assertServicesDeployed(c, map[string]serviceInfo{
   134  		"mysql": {
   135  			charm: "cs:xenial/mysql-42",
   136  			storage: map[string]state.StorageConstraints{
   137  				"data": state.StorageConstraints{Pool: "rootfs", Size: 50 * 1024, Count: 1},
   138  				"logs": state.StorageConstraints{Pool: "tmpfs", Size: 10 * 1024, Count: 1},
   139  			},
   140  		},
   141  		"wordpress": {charm: "cs:xenial/wordpress-47"},
   142  	})
   143  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   144  	s.assertUnitsCreated(c, map[string]string{
   145  		"mysql/0":     "0",
   146  		"wordpress/0": "1",
   147  	})
   148  }
   149  
   150  func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSpaceMissing(c *gc.C) {
   151  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   152  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings")
   153  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings")
   154  	output, err := runDeployCommand(c, "bundle/wordpress-with-endpoint-bindings")
   155  	c.Assert(err, gc.ErrorMatches,
   156  		"cannot deploy bundle: cannot deploy service \"mysql\": "+
   157  			"cannot add service \"mysql\": unknown space \"db\" not valid")
   158  	c.Assert(output, gc.Equals, "added charm cs:xenial/mysql-42")
   159  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42")
   160  	s.assertServicesDeployed(c, map[string]serviceInfo{})
   161  	s.assertUnitsCreated(c, map[string]string{})
   162  }
   163  
   164  func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSuccess(c *gc.C) {
   165  	_, err := s.State.AddSpace("db", "", nil, false)
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	_, err = s.State.AddSpace("public", "", nil, false)
   168  	c.Assert(err, jc.ErrorIsNil)
   169  
   170  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   171  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings")
   172  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings")
   173  	output, err := runDeployCommand(c, "bundle/wordpress-with-endpoint-bindings")
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	expectedOutput := `
   176  added charm cs:xenial/mysql-42
   177  service mysql deployed (charm cs:xenial/mysql-42 with the charm series "xenial")
   178  added charm cs:xenial/wordpress-extra-bindings-47
   179  service wordpress-extra-bindings deployed (charm cs:xenial/wordpress-extra-bindings-47 with the charm series "xenial")
   180  related wordpress-extra-bindings:db and mysql:server
   181  added mysql/0 unit to new machine
   182  added wordpress-extra-bindings/0 unit to new machine
   183  deployment of bundle "cs:bundle/wordpress-with-endpoint-bindings-1" completed`
   184  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   185  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-extra-bindings-47")
   186  
   187  	s.assertServicesDeployed(c, map[string]serviceInfo{
   188  		"mysql":                    {charm: "cs:xenial/mysql-42"},
   189  		"wordpress-extra-bindings": {charm: "cs:xenial/wordpress-extra-bindings-47"},
   190  	})
   191  	s.assertDeployedServiceBindings(c, map[string]serviceInfo{
   192  		"mysql": {
   193  			endpointBindings: map[string]string{"server": "db"},
   194  		},
   195  		"wordpress-extra-bindings": {
   196  			endpointBindings: map[string]string{
   197  				"cache":           "",
   198  				"url":             "public",
   199  				"logging-dir":     "",
   200  				"monitoring-port": "",
   201  				"db":              "db",
   202  				"cluster":         "",
   203  				"db-client":       "db",
   204  				"admin-api":       "public",
   205  				"foo-bar":         "",
   206  			},
   207  		},
   208  	})
   209  	s.assertRelationsEstablished(c, "wordpress-extra-bindings:cluster", "wordpress-extra-bindings:db mysql:server")
   210  	s.assertUnitsCreated(c, map[string]string{
   211  		"mysql/0":                    "0",
   212  		"wordpress-extra-bindings/0": "1",
   213  	})
   214  }
   215  
   216  func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwice(c *gc.C) {
   217  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   218  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   219  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   220  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	output, err := runDeployCommand(c, "bundle/wordpress-simple")
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	expectedOutput := `
   225  added charm cs:xenial/mysql-42
   226  reusing service mysql (charm: cs:xenial/mysql-42)
   227  added charm cs:xenial/wordpress-47
   228  reusing service wordpress (charm: cs:xenial/wordpress-47)
   229  wordpress:db and mysql:server are already related
   230  avoid adding new units to service mysql: 1 unit already present
   231  avoid adding new units to service wordpress: 1 unit already present
   232  deployment of bundle "cs:bundle/wordpress-simple-1" completed`
   233  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   234  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   235  	s.assertServicesDeployed(c, map[string]serviceInfo{
   236  		"mysql":     {charm: "cs:xenial/mysql-42"},
   237  		"wordpress": {charm: "cs:xenial/wordpress-47"},
   238  	})
   239  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   240  	s.assertUnitsCreated(c, map[string]string{
   241  		"mysql/0":     "0",
   242  		"wordpress/0": "1",
   243  	})
   244  }
   245  
   246  func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharm(c *gc.C) {
   247  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   248  	url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   249  	s.changeReadPerm(c, url, clientUserName)
   250  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   251  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47")
   254  	s.assertServicesDeployed(c, map[string]serviceInfo{
   255  		"mysql":     {charm: "cs:xenial/mysql-42"},
   256  		"wordpress": {charm: "cs:xenial/wordpress-47"},
   257  	})
   258  }
   259  
   260  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalPath(c *gc.C) {
   261  	dir := c.MkDir()
   262  	testcharms.Repo.ClonedDir(dir, "dummy")
   263  	path := filepath.Join(dir, "mybundle")
   264  	data := `
   265          series: xenial
   266          services:
   267              dummy:
   268                  charm: ./dummy
   269                  series: xenial
   270                  num_units: 1
   271      `
   272  	err := ioutil.WriteFile(path, []byte(data), 0644)
   273  	c.Assert(err, jc.ErrorIsNil)
   274  	output, err := runDeployCommand(c, path)
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	expectedOutput := fmt.Sprintf(`
   277  added charm local:xenial/dummy-1
   278  service dummy deployed (charm local:xenial/dummy-1 with the series "xenial" defined by the bundle)
   279  added dummy/0 unit to new machine
   280  deployment of bundle %q completed`, path)
   281  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   282  	s.assertCharmsUploaded(c, "local:xenial/dummy-1")
   283  	s.assertServicesDeployed(c, map[string]serviceInfo{
   284  		"dummy": {charm: "local:xenial/dummy-1"},
   285  	})
   286  }
   287  
   288  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNoSeriesInCharmURL(c *gc.C) {
   289  	testcharms.UploadCharmMultiSeries(c, s.client, "~who/multi-series", "multi-series")
   290  	dir := c.MkDir()
   291  	testcharms.Repo.ClonedDir(dir, "dummy")
   292  	path := filepath.Join(dir, "mybundle")
   293  	data := `
   294          series: trusty
   295          services:
   296              dummy:
   297                  charm: cs:~who/multi-series
   298      `
   299  	err := ioutil.WriteFile(path, []byte(data), 0644)
   300  	c.Assert(err, jc.ErrorIsNil)
   301  	output, err := runDeployCommand(c, path)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	expectedOutput := fmt.Sprintf(`
   304  added charm cs:~who/multi-series-0
   305  service dummy deployed (charm cs:~who/multi-series-0 with the series "trusty" defined by the bundle)
   306  deployment of bundle %q completed`, path)
   307  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   308  	s.assertCharmsUploaded(c, "cs:~who/multi-series-0")
   309  	s.assertServicesDeployed(c, map[string]serviceInfo{
   310  		"dummy": {charm: "cs:~who/multi-series-0"},
   311  	})
   312  }
   313  
   314  func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharmUnauthorized(c *gc.C) {
   315  	testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql")
   316  	url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress")
   317  	s.changeReadPerm(c, url, "who")
   318  	testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple")
   319  	_, err := runDeployCommand(c, "bundle/wordpress-simple")
   320  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: .*: unauthorized: access denied for user "client-username"`)
   321  }
   322  
   323  type BundleDeployCharmStoreSuite struct {
   324  	charmStoreSuite
   325  }
   326  
   327  var _ = gc.Suite(&BundleDeployCharmStoreSuite{})
   328  
   329  func (s *BundleDeployCharmStoreSuite) SetUpSuite(c *gc.C) {
   330  	s.charmStoreSuite.SetUpSuite(c)
   331  	s.PatchValue(&watcher.Period, 10*time.Millisecond)
   332  }
   333  
   334  func (s *BundleDeployCharmStoreSuite) Client() *csclient.Client {
   335  	return s.client
   336  }
   337  
   338  // DeployBundleYAML uses the given bundle content to create a bundle in the
   339  // local repository and then deploy it. It returns the bundle deployment output
   340  // and error.
   341  func (s *BundleDeployCharmStoreSuite) DeployBundleYAML(c *gc.C, content string) (string, error) {
   342  	bundlePath := filepath.Join(c.MkDir(), "example")
   343  	c.Assert(os.Mkdir(bundlePath, 0777), jc.ErrorIsNil)
   344  	defer os.RemoveAll(bundlePath)
   345  	err := ioutil.WriteFile(filepath.Join(bundlePath, "bundle.yaml"), []byte(content), 0644)
   346  	c.Assert(err, jc.ErrorIsNil)
   347  	err = ioutil.WriteFile(filepath.Join(bundlePath, "README.md"), []byte("README"), 0644)
   348  	c.Assert(err, jc.ErrorIsNil)
   349  	return runDeployCommand(c, bundlePath)
   350  }
   351  
   352  var deployBundleErrorsTests = []struct {
   353  	about   string
   354  	content string
   355  	err     string
   356  }{{
   357  	about: "local charm not found",
   358  	content: `
   359          services:
   360              mysql:
   361                  charm: ./mysql
   362                  num_units: 1
   363      `,
   364  	err: `the provided bundle has the following errors:
   365  charm path in service "mysql" does not exist: mysql`,
   366  }, {
   367  	about: "charm store charm not found",
   368  	content: `
   369          services:
   370              rails:
   371                  charm: xenial/rails-42
   372                  num_units: 1
   373      `,
   374  	err: `cannot deploy bundle: cannot resolve URL "xenial/rails-42": cannot resolve URL "cs:xenial/rails-42": charm not found`,
   375  }, {
   376  	about:   "invalid bundle content",
   377  	content: "!",
   378  	err:     `cannot unmarshal bundle data: YAML error: .*`,
   379  }, {
   380  	about: "invalid bundle data",
   381  	content: `
   382          services:
   383              mysql:
   384                  charm: mysql
   385                  num_units: -1
   386      `,
   387  	err: `the provided bundle has the following errors:
   388  negative number of units specified on service "mysql"`,
   389  }, {
   390  	about: "invalid constraints",
   391  	content: `
   392          services:
   393              mysql:
   394                  charm: mysql
   395                  num_units: 1
   396                  constraints: bad-wolf
   397      `,
   398  	err: `the provided bundle has the following errors:
   399  invalid constraints "bad-wolf" in service "mysql": malformed constraint "bad-wolf"`,
   400  }, {
   401  	about: "multiple bundle verification errors",
   402  	content: `
   403          services:
   404              mysql:
   405                  charm: mysql
   406                  num_units: -1
   407                  constraints: bad-wolf
   408      `,
   409  	err: `the provided bundle has the following errors:
   410  invalid constraints "bad-wolf" in service "mysql": malformed constraint "bad-wolf"
   411  negative number of units specified on service "mysql"`,
   412  }, {
   413  	about: "bundle inception",
   414  	content: `
   415          services:
   416              example:
   417                  charm: local:wordpress
   418                  num_units: 1
   419      `,
   420  	err: `cannot deploy bundle: cannot resolve URL "local:wordpress": unknown schema for charm URL "local:wordpress"`,
   421  }}
   422  
   423  func (s *BundleDeployCharmStoreSuite) TestDeployBundleErrors(c *gc.C) {
   424  	for i, test := range deployBundleErrorsTests {
   425  		c.Logf("test %d: %s", i, test.about)
   426  		_, err := s.DeployBundleYAML(c, test.content)
   427  		c.Check(err, gc.ErrorMatches, test.err)
   428  	}
   429  }
   430  
   431  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidOptions(c *gc.C) {
   432  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   433  	_, err := s.DeployBundleYAML(c, `
   434          services:
   435              wp:
   436                  charm: xenial/wordpress-42
   437                  num_units: 1
   438                  options:
   439                      blog-title: 42
   440      `)
   441  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy service "wp": option "blog-title" expected string, got 42`)
   442  }
   443  
   444  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidMachineContainerType(c *gc.C) {
   445  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   446  	_, err := s.DeployBundleYAML(c, `
   447          services:
   448              wp:
   449                  charm: xenial/wordpress
   450                  num_units: 1
   451                  to: ["bad:1"]
   452          machines:
   453              1:
   454      `)
   455  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot create machine for holding wp unit: invalid container type "bad"`)
   456  }
   457  
   458  func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidSeries(c *gc.C) {
   459  	testcharms.UploadCharm(c, s.client, "vivid/django-0", "dummy")
   460  	_, err := s.DeployBundleYAML(c, `
   461          services:
   462              django:
   463                  charm: vivid/django
   464                  num_units: 1
   465                  to:
   466                      - 1
   467          machines:
   468              1:
   469                  series: xenial
   470      `)
   471  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot add unit for service "django": adding new machine to host unit "django/0": cannot assign unit "django/0" to machine 0: series does not match`)
   472  }
   473  
   474  func (s *BundleDeployCharmStoreSuite) TestDeployBundleWatcherTimeout(c *gc.C) {
   475  	// Inject an "AllWatcher" that never delivers a result.
   476  	ch := make(chan struct{})
   477  	defer close(ch)
   478  	watcher := mockAllWatcher{
   479  		next: func() []multiwatcher.Delta {
   480  			<-ch
   481  			return nil
   482  		},
   483  	}
   484  	s.PatchValue(&watchAll, func(*api.Client) (allWatcher, error) {
   485  		return watcher, nil
   486  	})
   487  
   488  	testcharms.UploadCharm(c, s.client, "xenial/django-0", "dummy")
   489  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   490  	s.PatchValue(&updateUnitStatusPeriod, 0*time.Second)
   491  	_, err := s.DeployBundleYAML(c, `
   492          services:
   493              django:
   494                  charm: django
   495                  num_units: 1
   496              wordpress:
   497                  charm: wordpress
   498                  num_units: 1
   499                  to: [django]
   500      `)
   501  	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`)
   502  }
   503  
   504  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeployment(c *gc.C) {
   505  	charmsPath := c.MkDir()
   506  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   507  	wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress")
   508  	output, err := s.DeployBundleYAML(c, fmt.Sprintf(`
   509          series: xenial
   510          services:
   511              wordpress:
   512                  charm: %s
   513                  num_units: 1
   514              mysql:
   515                  charm: %s
   516                  num_units: 2
   517          relations:
   518              - ["wordpress:db", "mysql:server"]
   519      `, wordpressPath, mysqlPath))
   520  	c.Assert(err, jc.ErrorIsNil)
   521  	expectedOutput := `
   522  added charm local:xenial/mysql-1
   523  service mysql deployed (charm local:xenial/mysql-1 with the series "xenial" defined by the bundle)
   524  added charm local:xenial/wordpress-3
   525  service wordpress deployed (charm local:xenial/wordpress-3 with the series "xenial" defined by the bundle)
   526  related wordpress:db and mysql:server
   527  added mysql/0 unit to new machine
   528  added mysql/1 unit to new machine
   529  added wordpress/0 unit to new machine
   530  deployment of bundle "local:bundle/example-0" completed`
   531  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   532  	s.assertCharmsUploaded(c, "local:xenial/mysql-1", "local:xenial/wordpress-3")
   533  	s.assertServicesDeployed(c, map[string]serviceInfo{
   534  		"mysql":     {charm: "local:xenial/mysql-1"},
   535  		"wordpress": {charm: "local:xenial/wordpress-3"},
   536  	})
   537  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   538  	s.assertUnitsCreated(c, map[string]string{
   539  		"mysql/0":     "0",
   540  		"mysql/1":     "1",
   541  		"wordpress/0": "2",
   542  	})
   543  }
   544  
   545  func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalAndCharmStoreCharms(c *gc.C) {
   546  	charmsPath := c.MkDir()
   547  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   548  	mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql")
   549  	output, err := s.DeployBundleYAML(c, fmt.Sprintf(`
   550          series: xenial
   551          services:
   552              wordpress:
   553                  charm: xenial/wordpress-42
   554                  series: xenial
   555                  num_units: 1
   556              mysql:
   557                  charm: %s
   558                  num_units: 1
   559          relations:
   560              - ["wordpress:db", "mysql:server"]
   561      `, mysqlPath))
   562  	c.Assert(err, jc.ErrorIsNil)
   563  	expectedOutput := `
   564  added charm local:xenial/mysql-1
   565  service mysql deployed (charm local:xenial/mysql-1 with the series "xenial" defined by the bundle)
   566  added charm cs:xenial/wordpress-42
   567  service wordpress deployed (charm cs:xenial/wordpress-42 with the series "xenial" defined by the bundle)
   568  related wordpress:db and mysql:server
   569  added mysql/0 unit to new machine
   570  added wordpress/0 unit to new machine
   571  deployment of bundle "local:bundle/example-0" completed`
   572  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   573  	s.assertCharmsUploaded(c, "local:xenial/mysql-1", "cs:xenial/wordpress-42")
   574  	s.assertServicesDeployed(c, map[string]serviceInfo{
   575  		"mysql":     {charm: "local:xenial/mysql-1"},
   576  		"wordpress": {charm: "cs:xenial/wordpress-42"},
   577  	})
   578  	s.assertRelationsEstablished(c, "wordpress:db mysql:server")
   579  	s.assertUnitsCreated(c, map[string]string{
   580  		"mysql/0":     "0",
   581  		"wordpress/0": "1",
   582  	})
   583  }
   584  
   585  func (s *BundleDeployCharmStoreSuite) TestDeployBundleServiceOptions(c *gc.C) {
   586  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   587  	testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy")
   588  	output, err := s.DeployBundleYAML(c, `
   589          services:
   590              wordpress:
   591                  charm: wordpress
   592                  num_units: 1
   593                  options:
   594                      blog-title: these are the voyages
   595              customized:
   596                  charm: precise/dummy-0
   597                  num_units: 1
   598                  options:
   599                      username: who
   600                      skill-level: 47
   601      `)
   602  	c.Assert(err, jc.ErrorIsNil)
   603  	expectedOutput := `
   604  added charm cs:precise/dummy-0
   605  service customized deployed (charm cs:precise/dummy-0 with the series "precise" defined by the bundle)
   606  added charm cs:xenial/wordpress-42
   607  service wordpress deployed (charm cs:xenial/wordpress-42 with the charm series "xenial")
   608  added customized/0 unit to new machine
   609  added wordpress/0 unit to new machine
   610  deployment of bundle "local:bundle/example-0" completed`
   611  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   612  	s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42")
   613  	s.assertServicesDeployed(c, map[string]serviceInfo{
   614  		"customized": {
   615  			charm:  "cs:precise/dummy-0",
   616  			config: charm.Settings{"username": "who", "skill-level": int64(47)},
   617  		},
   618  		"wordpress": {
   619  			charm:  "cs:xenial/wordpress-42",
   620  			config: charm.Settings{"blog-title": "these are the voyages"},
   621  		},
   622  	})
   623  	s.assertUnitsCreated(c, map[string]string{
   624  		"wordpress/0":  "1",
   625  		"customized/0": "0",
   626  	})
   627  }
   628  
   629  func (s *BundleDeployCharmStoreSuite) TestDeployBundleServiceConstrants(c *gc.C) {
   630  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   631  	testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy")
   632  	output, err := s.DeployBundleYAML(c, `
   633          services:
   634              wordpress:
   635                  charm: wordpress
   636                  constraints: mem=4G cpu-cores=2
   637              customized:
   638                  charm: precise/dummy-0
   639                  num_units: 1
   640                  constraints: arch=i386
   641      `)
   642  	c.Assert(err, jc.ErrorIsNil)
   643  	expectedOutput := `
   644  added charm cs:precise/dummy-0
   645  service customized deployed (charm cs:precise/dummy-0 with the series "precise" defined by the bundle)
   646  added charm cs:xenial/wordpress-42
   647  service wordpress deployed (charm cs:xenial/wordpress-42 with the charm series "xenial")
   648  added customized/0 unit to new machine
   649  deployment of bundle "local:bundle/example-0" completed`
   650  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   651  	s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42")
   652  	s.assertServicesDeployed(c, map[string]serviceInfo{
   653  		"customized": {
   654  			charm:       "cs:precise/dummy-0",
   655  			constraints: constraints.MustParse("arch=i386"),
   656  		},
   657  		"wordpress": {
   658  			charm:       "cs:xenial/wordpress-42",
   659  			constraints: constraints.MustParse("mem=4G cpu-cores=2"),
   660  		},
   661  	})
   662  	s.assertUnitsCreated(c, map[string]string{
   663  		"customized/0": "0",
   664  	})
   665  }
   666  
   667  func (s *BundleDeployCharmStoreSuite) TestDeployBundleServiceUpgrade(c *gc.C) {
   668  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   669  	testcharms.UploadCharm(c, s.client, "vivid/upgrade-1", "upgrade1")
   670  	testcharms.UploadCharm(c, s.client, "vivid/upgrade-2", "upgrade2")
   671  
   672  	// First deploy the bundle.
   673  	output, err := s.DeployBundleYAML(c, `
   674          services:
   675              wordpress:
   676                  charm: wordpress
   677                  num_units: 1
   678                  options:
   679                      blog-title: these are the voyages
   680                  constraints: spaces=final,frontiers mem=8000M
   681              up:
   682                  charm: vivid/upgrade-1
   683                  num_units: 1
   684      `)
   685  	c.Assert(err, jc.ErrorIsNil)
   686  	expectedOutput := `
   687  added charm cs:vivid/upgrade-1
   688  service up deployed (charm cs:vivid/upgrade-1 with the series "vivid" defined by the bundle)
   689  added charm cs:xenial/wordpress-42
   690  service wordpress deployed (charm cs:xenial/wordpress-42 with the charm series "xenial")
   691  added up/0 unit to new machine
   692  added wordpress/0 unit to new machine
   693  deployment of bundle "local:bundle/example-0" completed`
   694  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   695  	s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:xenial/wordpress-42")
   696  
   697  	// Then deploy a new bundle with modified charm revision and options.
   698  	output, err = s.DeployBundleYAML(c, `
   699          services:
   700              wordpress:
   701                  charm: wordpress
   702                  num_units: 1
   703                  options:
   704                      blog-title: new title
   705                  constraints: spaces=new cpu-cores=8
   706              up:
   707                  charm: vivid/upgrade-2
   708                  num_units: 1
   709      `)
   710  	c.Assert(err, jc.ErrorIsNil)
   711  	expectedOutput = `
   712  added charm cs:vivid/upgrade-2
   713  upgraded charm for existing service up (from cs:vivid/upgrade-1 to cs:vivid/upgrade-2)
   714  added charm cs:xenial/wordpress-42
   715  reusing service wordpress (charm: cs:xenial/wordpress-42)
   716  configuration updated for service wordpress
   717  constraints applied for service wordpress
   718  avoid adding new units to service up: 1 unit already present
   719  avoid adding new units to service wordpress: 1 unit already present
   720  deployment of bundle "local:bundle/example-0" completed`
   721  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   722  	s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:vivid/upgrade-2", "cs:xenial/wordpress-42")
   723  	s.assertServicesDeployed(c, map[string]serviceInfo{
   724  		"up": {charm: "cs:vivid/upgrade-2"},
   725  		"wordpress": {
   726  			charm:       "cs:xenial/wordpress-42",
   727  			config:      charm.Settings{"blog-title": "new title"},
   728  			constraints: constraints.MustParse("spaces=new cpu-cores=8"),
   729  		},
   730  	})
   731  	s.assertUnitsCreated(c, map[string]string{
   732  		"up/0":        "0",
   733  		"wordpress/0": "1",
   734  	})
   735  }
   736  
   737  func (s *BundleDeployCharmStoreSuite) TestDeployBundleExpose(c *gc.C) {
   738  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress")
   739  	content := `
   740          services:
   741              wordpress:
   742                  charm: wordpress
   743                  num_units: 1
   744                  expose: true
   745      `
   746  	expectedServices := map[string]serviceInfo{
   747  		"wordpress": {
   748  			charm:   "cs:xenial/wordpress-42",
   749  			exposed: true,
   750  		},
   751  	}
   752  
   753  	// First deploy the bundle.
   754  	output, err := s.DeployBundleYAML(c, content)
   755  	c.Assert(err, jc.ErrorIsNil)
   756  	expectedOutput := `
   757  added charm cs:xenial/wordpress-42
   758  service wordpress deployed (charm cs:xenial/wordpress-42 with the charm series "xenial")
   759  service wordpress exposed
   760  added wordpress/0 unit to new machine
   761  deployment of bundle "local:bundle/example-0" completed`
   762  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   763  	s.assertServicesDeployed(c, expectedServices)
   764  
   765  	// Then deploy the same bundle again: no error is produced when the service
   766  	// is exposed again.
   767  	output, err = s.DeployBundleYAML(c, content)
   768  	c.Assert(err, jc.ErrorIsNil)
   769  	expectedOutput = `
   770  added charm cs:xenial/wordpress-42
   771  reusing service wordpress (charm: cs:xenial/wordpress-42)
   772  service wordpress exposed
   773  avoid adding new units to service wordpress: 1 unit already present
   774  deployment of bundle "local:bundle/example-0" completed`
   775  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   776  	s.assertServicesDeployed(c, expectedServices)
   777  
   778  	// Then deploy a bundle with the service unexposed, and check that the
   779  	// service is not unexposed.
   780  	output, err = s.DeployBundleYAML(c, `
   781          services:
   782              wordpress:
   783                  charm: wordpress
   784                  num_units: 1
   785                  expose: false
   786      `)
   787  	c.Assert(err, jc.ErrorIsNil)
   788  	expectedOutput = `
   789  added charm cs:xenial/wordpress-42
   790  reusing service wordpress (charm: cs:xenial/wordpress-42)
   791  avoid adding new units to service wordpress: 1 unit already present
   792  deployment of bundle "local:bundle/example-0" completed`
   793  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   794  	s.assertServicesDeployed(c, expectedServices)
   795  }
   796  
   797  func (s *BundleDeployCharmStoreSuite) TestDeployBundleServiceUpgradeFailure(c *gc.C) {
   798  	s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   799  
   800  	// Try upgrading to a different charm name.
   801  	testcharms.UploadCharm(c, s.client, "xenial/incompatible-42", "wordpress")
   802  	_, err := s.DeployBundleYAML(c, `
   803          services:
   804              wordpress:
   805                  charm: xenial/incompatible-42
   806                  num_units: 1
   807      `)
   808  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:xenial/incompatible-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
   809  
   810  	// Try upgrading to a different series.
   811  	// Note that this test comes before the next one because
   812  	// otherwise we can't resolve the charm URL because the charm's
   813  	// "base entity" is not marked as promulgated so the query by
   814  	// promulgated will find it.
   815  	testcharms.UploadCharm(c, s.client, "vivid/wordpress-42", "wordpress")
   816  	_, err = s.DeployBundleYAML(c, `
   817          services:
   818              wordpress:
   819                  charm: vivid/wordpress
   820                  num_units: 1
   821      `)
   822  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:vivid/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
   823  
   824  	// Try upgrading to a different user.
   825  	testcharms.UploadCharm(c, s.client, "~who/xenial/wordpress-42", "wordpress")
   826  	_, err = s.DeployBundleYAML(c, `
   827          services:
   828              wordpress:
   829                  charm: cs:~who/xenial/wordpress-42
   830                  num_units: 1
   831      `)
   832  	c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade service "wordpress": bundle charm "cs:~who/xenial/wordpress-42" is incompatible with existing charm "local:quantal/wordpress-3"`)
   833  }
   834  
   835  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMultipleRelations(c *gc.C) {
   836  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   837  	testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql")
   838  	testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql")
   839  	testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish")
   840  	output, err := s.DeployBundleYAML(c, `
   841          services:
   842              wp:
   843                  charm: wordpress
   844                  num_units: 1
   845              mysql:
   846                  charm: mysql
   847                  num_units: 1
   848              pgres:
   849                  charm: xenial/postgres-2
   850                  num_units: 1
   851              varnish:
   852                  charm: xenial/varnish
   853                  num_units: 1
   854          relations:
   855              - ["wp:db", "mysql:server"]
   856              - ["wp:db", "pgres:server"]
   857              - ["varnish:webcache", "wp:cache"]
   858      `)
   859  	c.Assert(err, jc.ErrorIsNil)
   860  	expectedOutput := `
   861  added charm cs:xenial/mysql-1
   862  service mysql deployed (charm cs:xenial/mysql-1 with the charm series "xenial")
   863  added charm cs:xenial/postgres-2
   864  service pgres deployed (charm cs:xenial/postgres-2 with the series "xenial" defined by the bundle)
   865  added charm cs:xenial/varnish-3
   866  service varnish deployed (charm cs:xenial/varnish-3 with the series "xenial" defined by the bundle)
   867  added charm cs:xenial/wordpress-0
   868  service wp deployed (charm cs:xenial/wordpress-0 with the charm series "xenial")
   869  related wp:db and mysql:server
   870  related wp:db and pgres:server
   871  related varnish:webcache and wp:cache
   872  added mysql/0 unit to new machine
   873  added pgres/0 unit to new machine
   874  added varnish/0 unit to new machine
   875  added wp/0 unit to new machine
   876  deployment of bundle "local:bundle/example-0" completed`
   877  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   878  	s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:db pgres:server", "wp:cache varnish:webcache")
   879  	s.assertUnitsCreated(c, map[string]string{
   880  		"mysql/0":   "0",
   881  		"pgres/0":   "1",
   882  		"varnish/0": "2",
   883  		"wp/0":      "3",
   884  	})
   885  }
   886  
   887  func (s *BundleDeployCharmStoreSuite) TestDeployBundleNewRelations(c *gc.C) {
   888  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   889  	testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql")
   890  	testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql")
   891  	testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish")
   892  	_, err := s.DeployBundleYAML(c, `
   893          services:
   894              wp:
   895                  charm: wordpress
   896                  num_units: 1
   897              mysql:
   898                  charm: mysql
   899                  num_units: 1
   900              varnish:
   901                  charm: xenial/varnish
   902                  num_units: 1
   903          relations:
   904              - ["wp:db", "mysql:server"]
   905      `)
   906  	c.Assert(err, jc.ErrorIsNil)
   907  	output, err := s.DeployBundleYAML(c, `
   908          services:
   909              wp:
   910                  charm: wordpress
   911                  num_units: 1
   912              mysql:
   913                  charm: mysql
   914                  num_units: 1
   915              varnish:
   916                  charm: xenial/varnish
   917                  num_units: 1
   918          relations:
   919              - ["wp:db", "mysql:server"]
   920              - ["varnish:webcache", "wp:cache"]
   921      `)
   922  	c.Assert(err, jc.ErrorIsNil)
   923  	expectedOutput := `
   924  added charm cs:xenial/mysql-1
   925  reusing service mysql (charm: cs:xenial/mysql-1)
   926  added charm cs:xenial/varnish-3
   927  reusing service varnish (charm: cs:xenial/varnish-3)
   928  added charm cs:xenial/wordpress-0
   929  reusing service wp (charm: cs:xenial/wordpress-0)
   930  wp:db and mysql:server are already related
   931  related varnish:webcache and wp:cache
   932  avoid adding new units to service mysql: 1 unit already present
   933  avoid adding new units to service varnish: 1 unit already present
   934  avoid adding new units to service wp: 1 unit already present
   935  deployment of bundle "local:bundle/example-0" completed`
   936  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   937  	s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:cache varnish:webcache")
   938  	s.assertUnitsCreated(c, map[string]string{
   939  		"mysql/0":   "0",
   940  		"varnish/0": "1",
   941  		"wp/0":      "2",
   942  	})
   943  }
   944  
   945  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachinesUnitsPlacement(c *gc.C) {
   946  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
   947  	testcharms.UploadCharm(c, s.client, "xenial/mysql-2", "mysql")
   948  	content := `
   949          services:
   950              wp:
   951                  charm: cs:xenial/wordpress-0
   952                  num_units: 2
   953                  to:
   954                      - 1
   955                      - lxc:2
   956                  options:
   957                      blog-title: these are the voyages
   958              sql:
   959                  charm: cs:xenial/mysql
   960                  num_units: 2
   961                  to:
   962                      - lxc:wp/0
   963                      - new
   964          machines:
   965              1:
   966                  series: xenial
   967              2:
   968      `
   969  	output, err := s.DeployBundleYAML(c, content)
   970  	c.Assert(err, jc.ErrorIsNil)
   971  	expectedOutput := `
   972  added charm cs:xenial/mysql-2
   973  service sql deployed (charm cs:xenial/mysql-2 with the series "xenial" defined by the bundle)
   974  added charm cs:xenial/wordpress-0
   975  service wp deployed (charm cs:xenial/wordpress-0 with the series "xenial" defined by the bundle)
   976  created new machine 0 for holding wp unit
   977  created new machine 1 for holding wp unit
   978  added wp/0 unit to machine 0
   979  created 0/lxc/0 container in machine 0 for holding sql unit
   980  created new machine 2 for holding sql unit
   981  created 1/lxc/0 container in machine 1 for holding wp unit
   982  added sql/0 unit to machine 0/lxc/0
   983  added sql/1 unit to machine 2
   984  added wp/1 unit to machine 1/lxc/0
   985  deployment of bundle "local:bundle/example-0" completed`
   986  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   987  	s.assertServicesDeployed(c, map[string]serviceInfo{
   988  		"sql": {charm: "cs:xenial/mysql-2"},
   989  		"wp": {
   990  			charm:  "cs:xenial/wordpress-0",
   991  			config: charm.Settings{"blog-title": "these are the voyages"},
   992  		},
   993  	})
   994  	s.assertRelationsEstablished(c)
   995  
   996  	// We explicitly pull out the map creation in the call to
   997  	// s.assertUnitsCreated() and create the map as a new variable
   998  	// because this /appears/ to tickle a bug on ppc64le using
   999  	// gccgo-4.9; the bug is that the map on the receiving side
  1000  	// does not have the same contents as it does here - which is
  1001  	// weird because that pattern is used elsewhere in this
  1002  	// function. And just pulling the map instantiation out of the
  1003  	// call is not enough; we need to do something benign with the
  1004  	// variable to keep a reference beyond the call to the
  1005  	// s.assertUnitsCreated(). I have to chosen to delete a
  1006  	// non-existent key. This problem does not occur on amd64
  1007  	// using gc or gccgo-4.9. Nor does it happen using go1.6 on
  1008  	// ppc64. Once we switch to go1.6 across the board this change
  1009  	// should be reverted. See http://pad.lv/1556116.
  1010  	expectedUnits := map[string]string{
  1011  		"sql/0": "0/lxc/0",
  1012  		"sql/1": "2",
  1013  		"wp/0":  "0",
  1014  		"wp/1":  "1/lxc/0",
  1015  	}
  1016  	s.assertUnitsCreated(c, expectedUnits)
  1017  	delete(expectedUnits, "non-existent")
  1018  
  1019  	// Redeploy the same bundle again.
  1020  	output, err = s.DeployBundleYAML(c, content)
  1021  	c.Assert(err, jc.ErrorIsNil)
  1022  	expectedOutput = `
  1023  added charm cs:xenial/mysql-2
  1024  reusing service sql (charm: cs:xenial/mysql-2)
  1025  added charm cs:xenial/wordpress-0
  1026  reusing service wp (charm: cs:xenial/wordpress-0)
  1027  configuration updated for service wp
  1028  avoid creating other machines to host wp units
  1029  avoid adding new units to service wp: 2 units already present
  1030  avoid creating other machines to host sql units
  1031  avoid adding new units to service sql: 2 units already present
  1032  deployment of bundle "local:bundle/example-0" completed`
  1033  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1034  	s.assertUnitsCreated(c, map[string]string{
  1035  		"sql/0": "0/lxc/0",
  1036  		"sql/1": "2",
  1037  		"wp/0":  "0",
  1038  		"wp/1":  "1/lxc/0",
  1039  	})
  1040  }
  1041  
  1042  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachineAttributes(c *gc.C) {
  1043  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1044  	output, err := s.DeployBundleYAML(c, `
  1045          services:
  1046              django:
  1047                  charm: cs:xenial/django-42
  1048                  num_units: 2
  1049                  to:
  1050                      - 1
  1051                      - new
  1052          machines:
  1053              1:
  1054                  series: xenial
  1055                  constraints: "cpu-cores=4 mem=4G"
  1056                  annotations:
  1057                      foo: bar
  1058      `)
  1059  	c.Assert(err, jc.ErrorIsNil)
  1060  	expectedOutput := `
  1061  added charm cs:xenial/django-42
  1062  service django deployed (charm cs:xenial/django-42 with the series "xenial" defined by the bundle)
  1063  created new machine 0 for holding django unit
  1064  annotations set for machine 0
  1065  added django/0 unit to machine 0
  1066  created new machine 1 for holding django unit
  1067  added django/1 unit to machine 1
  1068  deployment of bundle "local:bundle/example-0" completed`
  1069  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1070  	s.assertServicesDeployed(c, map[string]serviceInfo{
  1071  		"django": {charm: "cs:xenial/django-42"},
  1072  	})
  1073  	s.assertRelationsEstablished(c)
  1074  	s.assertUnitsCreated(c, map[string]string{
  1075  		"django/0": "0",
  1076  		"django/1": "1",
  1077  	})
  1078  	m, err := s.State.Machine("0")
  1079  	c.Assert(err, jc.ErrorIsNil)
  1080  	c.Assert(m.Series(), gc.Equals, "xenial")
  1081  	cons, err := m.Constraints()
  1082  	c.Assert(err, jc.ErrorIsNil)
  1083  	expectedCons, err := constraints.Parse("cpu-cores=4 mem=4G")
  1084  	c.Assert(err, jc.ErrorIsNil)
  1085  	c.Assert(cons, jc.DeepEquals, expectedCons)
  1086  	ann, err := s.State.Annotations(m)
  1087  	c.Assert(err, jc.ErrorIsNil)
  1088  	c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"})
  1089  }
  1090  
  1091  func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwiceScaleUp(c *gc.C) {
  1092  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1093  	_, err := s.DeployBundleYAML(c, `
  1094          services:
  1095              django:
  1096                  charm: cs:xenial/django-42
  1097                  num_units: 2
  1098      `)
  1099  	c.Assert(err, jc.ErrorIsNil)
  1100  	output, err := s.DeployBundleYAML(c, `
  1101          services:
  1102              django:
  1103                  charm: cs:xenial/django-42
  1104                  num_units: 5
  1105      `)
  1106  	c.Assert(err, jc.ErrorIsNil)
  1107  	expectedOutput := `
  1108  added charm cs:xenial/django-42
  1109  reusing service django (charm: cs:xenial/django-42)
  1110  added django/2 unit to new machine
  1111  added django/3 unit to new machine
  1112  added django/4 unit to new machine
  1113  avoid adding new units to service django: 5 units already present
  1114  deployment of bundle "local:bundle/example-0" completed`
  1115  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1116  	s.assertUnitsCreated(c, map[string]string{
  1117  		"django/0": "0",
  1118  		"django/1": "1",
  1119  		"django/2": "2",
  1120  		"django/3": "3",
  1121  		"django/4": "4",
  1122  	})
  1123  }
  1124  
  1125  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedInService(c *gc.C) {
  1126  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1127  	testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress")
  1128  	output, err := s.DeployBundleYAML(c, `
  1129          services:
  1130              wordpress:
  1131                  charm: wordpress
  1132                  num_units: 3
  1133              django:
  1134                  charm: cs:xenial/django-42
  1135                  num_units: 2
  1136                  to: [wordpress]
  1137      `)
  1138  	c.Assert(err, jc.ErrorIsNil)
  1139  	expectedOutput := `
  1140  added charm cs:xenial/django-42
  1141  service django deployed (charm cs:xenial/django-42 with the series "xenial" defined by the bundle)
  1142  added charm cs:xenial/wordpress-0
  1143  service wordpress deployed (charm cs:xenial/wordpress-0 with the charm series "xenial")
  1144  added wordpress/0 unit to new machine
  1145  added wordpress/1 unit to new machine
  1146  added wordpress/2 unit to new machine
  1147  added django/0 unit to machine 0
  1148  added django/1 unit to machine 1
  1149  deployment of bundle "local:bundle/example-0" completed`
  1150  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1151  	s.assertUnitsCreated(c, map[string]string{
  1152  		"django/0":    "0",
  1153  		"django/1":    "1",
  1154  		"wordpress/0": "0",
  1155  		"wordpress/1": "1",
  1156  		"wordpress/2": "2",
  1157  	})
  1158  }
  1159  
  1160  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitColocationWithUnit(c *gc.C) {
  1161  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1162  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1163  	testcharms.UploadCharm(c, s.client, "xenial/rails-0", "dummy")
  1164  	output, err := s.DeployBundleYAML(c, `
  1165          services:
  1166              memcached:
  1167                  charm: cs:xenial/mem-47
  1168                  num_units: 3
  1169                  to: [1, new]
  1170              django:
  1171                  charm: cs:xenial/django-42
  1172                  num_units: 5
  1173                  to:
  1174                      - memcached/0
  1175                      - lxc:memcached/1
  1176                      - lxc:memcached/2
  1177                      - kvm:ror
  1178              ror:
  1179                  charm: rails
  1180                  num_units: 2
  1181                  to:
  1182                      - new
  1183                      - 1
  1184          machines:
  1185              1:
  1186                  series: xenial
  1187      `)
  1188  	c.Assert(err, jc.ErrorIsNil)
  1189  	expectedOutput := `
  1190  added charm cs:xenial/django-42
  1191  service django deployed (charm cs:xenial/django-42 with the series "xenial" defined by the bundle)
  1192  added charm cs:xenial/mem-47
  1193  service memcached deployed (charm cs:xenial/mem-47 with the series "xenial" defined by the bundle)
  1194  added charm cs:xenial/rails-0
  1195  service ror deployed (charm cs:xenial/rails-0 with the charm series "xenial")
  1196  created new machine 0 for holding memcached and ror units
  1197  added memcached/0 unit to machine 0
  1198  added ror/0 unit to machine 0
  1199  created 0/kvm/0 container in machine 0 for holding django unit
  1200  created new machine 1 for holding memcached unit
  1201  created new machine 2 for holding memcached unit
  1202  created new machine 3 for holding ror unit
  1203  added django/0 unit to machine 0
  1204  added django/1 unit to machine 0/kvm/0
  1205  added memcached/1 unit to machine 1
  1206  added memcached/2 unit to machine 2
  1207  added ror/1 unit to machine 3
  1208  created 1/lxc/0 container in machine 1 for holding django unit
  1209  created 2/lxc/0 container in machine 2 for holding django unit
  1210  created 3/kvm/0 container in machine 3 for holding django unit
  1211  added django/2 unit to machine 1/lxc/0
  1212  added django/3 unit to machine 2/lxc/0
  1213  added django/4 unit to machine 3/kvm/0
  1214  deployment of bundle "local:bundle/example-0" completed`
  1215  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1216  	s.assertUnitsCreated(c, map[string]string{
  1217  		"django/0":    "0",
  1218  		"django/1":    "0/kvm/0",
  1219  		"django/2":    "1/lxc/0",
  1220  		"django/3":    "2/lxc/0",
  1221  		"django/4":    "3/kvm/0",
  1222  		"memcached/0": "0",
  1223  		"memcached/1": "1",
  1224  		"memcached/2": "2",
  1225  		"ror/0":       "0",
  1226  		"ror/1":       "3",
  1227  	})
  1228  }
  1229  
  1230  func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedToMachines(c *gc.C) {
  1231  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1232  	output, err := s.DeployBundleYAML(c, `
  1233          services:
  1234              django:
  1235                  charm: cs:django
  1236                  num_units: 7
  1237                  to:
  1238                      - new
  1239                      - 4
  1240                      - kvm:8
  1241                      - lxc:4
  1242                      - lxc:4
  1243                      - lxc:new
  1244          machines:
  1245              4:
  1246              8:
  1247      `)
  1248  	c.Assert(err, jc.ErrorIsNil)
  1249  	expectedOutput := `
  1250  added charm cs:xenial/django-42
  1251  service django deployed (charm cs:xenial/django-42 with the charm series "xenial")
  1252  created new machine 0 for holding django unit
  1253  created new machine 1 for holding django unit
  1254  added django/0 unit to machine 0
  1255  created new machine 2 for holding django unit
  1256  created 1/kvm/0 container in machine 1 for holding django unit
  1257  created 0/lxc/0 container in machine 0 for holding django unit
  1258  created 0/lxc/1 container in machine 0 for holding django unit
  1259  created 3/lxc/0 container in new machine for holding django unit
  1260  created 4/lxc/0 container in new machine for holding django unit
  1261  added django/1 unit to machine 2
  1262  added django/2 unit to machine 1/kvm/0
  1263  added django/3 unit to machine 0/lxc/0
  1264  added django/4 unit to machine 0/lxc/1
  1265  added django/5 unit to machine 3/lxc/0
  1266  added django/6 unit to machine 4/lxc/0
  1267  deployment of bundle "local:bundle/example-0" completed`
  1268  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1269  	s.assertUnitsCreated(c, map[string]string{
  1270  		"django/0": "0",       // Machine "4" in the bundle.
  1271  		"django/1": "2",       // Machine "new" in the bundle.
  1272  		"django/2": "1/kvm/0", // The KVM container in bundle machine "8".
  1273  		"django/3": "0/lxc/0", // First LXC container in bundle machine "4".
  1274  		"django/4": "0/lxc/1", // Second LXC container in bundle machine "4".
  1275  		"django/5": "3/lxc/0", // First LXC in new machine.
  1276  		"django/6": "4/lxc/0", // Second LXC in new machine.
  1277  	})
  1278  }
  1279  
  1280  func (s *BundleDeployCharmStoreSuite) TestDeployBundleMassiveUnitColocation(c *gc.C) {
  1281  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1282  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1283  	testcharms.UploadCharm(c, s.client, "xenial/rails-0", "dummy")
  1284  	output, err := s.DeployBundleYAML(c, `
  1285          services:
  1286              memcached:
  1287                  charm: cs:xenial/mem-47
  1288                  num_units: 3
  1289                  to: [1, 2, 3]
  1290              django:
  1291                  charm: cs:xenial/django-42
  1292                  num_units: 4
  1293                  to:
  1294                      - 1
  1295                      - lxc:memcached
  1296              ror:
  1297                  charm: rails
  1298                  num_units: 3
  1299                  to:
  1300                      - 1
  1301                      - kvm:3
  1302          machines:
  1303              1:
  1304              2:
  1305              3:
  1306      `)
  1307  	c.Assert(err, jc.ErrorIsNil)
  1308  	expectedOutput := `
  1309  added charm cs:xenial/django-42
  1310  service django deployed (charm cs:xenial/django-42 with the series "xenial" defined by the bundle)
  1311  added charm cs:xenial/mem-47
  1312  service memcached deployed (charm cs:xenial/mem-47 with the series "xenial" defined by the bundle)
  1313  added charm cs:xenial/rails-0
  1314  service ror deployed (charm cs:xenial/rails-0 with the charm series "xenial")
  1315  created new machine 0 for holding django, memcached and ror units
  1316  created new machine 1 for holding memcached unit
  1317  created new machine 2 for holding memcached and ror units
  1318  added django/0 unit to machine 0
  1319  added memcached/0 unit to machine 0
  1320  added memcached/1 unit to machine 1
  1321  added memcached/2 unit to machine 2
  1322  added ror/0 unit to machine 0
  1323  created 0/lxc/0 container in machine 0 for holding django unit
  1324  created 1/lxc/0 container in machine 1 for holding django unit
  1325  created 2/lxc/0 container in machine 2 for holding django unit
  1326  created 2/kvm/0 container in machine 2 for holding ror unit
  1327  created 2/kvm/1 container in machine 2 for holding ror unit
  1328  added django/1 unit to machine 0/lxc/0
  1329  added django/2 unit to machine 1/lxc/0
  1330  added django/3 unit to machine 2/lxc/0
  1331  added ror/1 unit to machine 2/kvm/0
  1332  added ror/2 unit to machine 2/kvm/1
  1333  deployment of bundle "local:bundle/example-0" completed`
  1334  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1335  	s.assertUnitsCreated(c, map[string]string{
  1336  		"django/0":    "0",
  1337  		"django/1":    "0/lxc/0",
  1338  		"django/2":    "1/lxc/0",
  1339  		"django/3":    "2/lxc/0",
  1340  		"memcached/0": "0",
  1341  		"memcached/1": "1",
  1342  		"memcached/2": "2",
  1343  		"ror/0":       "0",
  1344  		"ror/1":       "2/kvm/0",
  1345  		"ror/2":       "2/kvm/1",
  1346  	})
  1347  
  1348  	// Redeploy a very similar bundle with another service unit. The new unit
  1349  	// is placed on machine 1 because that's the least crowded machine.
  1350  	content := `
  1351          services:
  1352              memcached:
  1353                  charm: cs:xenial/mem-47
  1354                  num_units: 3
  1355                  to: [1, 2, 3]
  1356              django:
  1357                  charm: cs:xenial/django-42
  1358                  num_units: 4
  1359                  to:
  1360                      - 1
  1361                      - lxc:memcached
  1362              node:
  1363                  charm: cs:xenial/django-42
  1364                  num_units: 1
  1365                  to:
  1366                      - lxc:memcached
  1367          machines:
  1368              1:
  1369              2:
  1370              3:
  1371      `
  1372  	output, err = s.DeployBundleYAML(c, content)
  1373  	c.Assert(err, jc.ErrorIsNil)
  1374  	expectedOutput = `
  1375  added charm cs:xenial/django-42
  1376  reusing service django (charm: cs:xenial/django-42)
  1377  added charm cs:xenial/mem-47
  1378  reusing service memcached (charm: cs:xenial/mem-47)
  1379  service node deployed (charm cs:xenial/django-42 with the series "xenial" defined by the bundle)
  1380  avoid creating other machines to host django and memcached units
  1381  avoid adding new units to service django: 4 units already present
  1382  avoid adding new units to service memcached: 3 units already present
  1383  created 1/lxc/1 container in machine 1 for holding node unit
  1384  added node/0 unit to machine 1/lxc/1
  1385  deployment of bundle "local:bundle/example-0" completed`
  1386  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1387  
  1388  	// Redeploy the same bundle again and check that nothing happens.
  1389  	output, err = s.DeployBundleYAML(c, content)
  1390  	c.Assert(err, jc.ErrorIsNil)
  1391  	expectedOutput = `
  1392  added charm cs:xenial/django-42
  1393  reusing service django (charm: cs:xenial/django-42)
  1394  added charm cs:xenial/mem-47
  1395  reusing service memcached (charm: cs:xenial/mem-47)
  1396  reusing service node (charm: cs:xenial/django-42)
  1397  avoid creating other machines to host django and memcached units
  1398  avoid adding new units to service django: 4 units already present
  1399  avoid adding new units to service memcached: 3 units already present
  1400  avoid creating other machines to host node units
  1401  avoid adding new units to service node: 1 unit already present
  1402  deployment of bundle "local:bundle/example-0" completed`
  1403  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1404  	s.assertUnitsCreated(c, map[string]string{
  1405  		"django/0":    "0",
  1406  		"django/1":    "0/lxc/0",
  1407  		"django/2":    "1/lxc/0",
  1408  		"django/3":    "2/lxc/0",
  1409  		"memcached/0": "0",
  1410  		"memcached/1": "1",
  1411  		"memcached/2": "2",
  1412  		"node/0":      "1/lxc/1",
  1413  		"ror/0":       "0",
  1414  		"ror/1":       "2/kvm/0",
  1415  		"ror/2":       "2/kvm/1",
  1416  	})
  1417  }
  1418  
  1419  func (s *BundleDeployCharmStoreSuite) TestDeployBundleAnnotations(c *gc.C) {
  1420  	testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy")
  1421  	testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy")
  1422  	output, err := s.DeployBundleYAML(c, `
  1423          services:
  1424              django:
  1425                  charm: cs:django
  1426                  num_units: 1
  1427                  annotations:
  1428                      key1: value1
  1429                      key2: value2
  1430                  to: [1]
  1431              memcached:
  1432                  charm: xenial/mem-47
  1433                  num_units: 1
  1434          machines:
  1435              1:
  1436                  annotations: {foo: bar}
  1437      `)
  1438  	c.Assert(err, jc.ErrorIsNil)
  1439  	expectedOutput := `
  1440  added charm cs:xenial/django-42
  1441  service django deployed (charm cs:xenial/django-42 with the charm series "xenial")
  1442  annotations set for service django
  1443  added charm cs:xenial/mem-47
  1444  service memcached deployed (charm cs:xenial/mem-47 with the series "xenial" defined by the bundle)
  1445  created new machine 0 for holding django unit
  1446  annotations set for machine 0
  1447  added django/0 unit to machine 0
  1448  added memcached/0 unit to new machine
  1449  deployment of bundle "local:bundle/example-0" completed`
  1450  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1451  	svc, err := s.State.Service("django")
  1452  	c.Assert(err, jc.ErrorIsNil)
  1453  	ann, err := s.State.Annotations(svc)
  1454  	c.Assert(err, jc.ErrorIsNil)
  1455  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1456  		"key1": "value1",
  1457  		"key2": "value2",
  1458  	})
  1459  	m, err := s.State.Machine("0")
  1460  	c.Assert(err, jc.ErrorIsNil)
  1461  	ann, err = s.State.Annotations(m)
  1462  	c.Assert(err, jc.ErrorIsNil)
  1463  	c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"})
  1464  
  1465  	// Update the annotations and deploy the bundle again.
  1466  	output, err = s.DeployBundleYAML(c, `
  1467          services:
  1468              django:
  1469                  charm: cs:django
  1470                  num_units: 1
  1471                  annotations:
  1472                      key1: new value!
  1473                      key2: value2
  1474                  to: [1]
  1475          machines:
  1476              1:
  1477                  annotations: {answer: 42}
  1478      `)
  1479  	c.Assert(err, jc.ErrorIsNil)
  1480  	expectedOutput = `
  1481  added charm cs:xenial/django-42
  1482  reusing service django (charm: cs:xenial/django-42)
  1483  annotations set for service django
  1484  avoid creating other machines to host django units
  1485  annotations set for machine 0
  1486  avoid adding new units to service django: 1 unit already present
  1487  deployment of bundle "local:bundle/example-0" completed`
  1488  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
  1489  	ann, err = s.State.Annotations(svc)
  1490  	c.Assert(err, jc.ErrorIsNil)
  1491  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1492  		"key1": "new value!",
  1493  		"key2": "value2",
  1494  	})
  1495  	ann, err = s.State.Annotations(m)
  1496  	c.Assert(err, jc.ErrorIsNil)
  1497  	c.Assert(ann, jc.DeepEquals, map[string]string{
  1498  		"foo":    "bar",
  1499  		"answer": "42",
  1500  	})
  1501  }
  1502  
  1503  type mockAllWatcher struct {
  1504  	next func() []multiwatcher.Delta
  1505  }
  1506  
  1507  func (w mockAllWatcher) Next() ([]multiwatcher.Delta, error) {
  1508  	return w.next(), nil
  1509  }
  1510  
  1511  func (mockAllWatcher) Stop() error {
  1512  	return nil
  1513  }