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 }