github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/application_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 jc "github.com/juju/testing/checkers" 13 jujutxn "github.com/juju/txn" 14 "github.com/juju/utils/set" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/charm.v6-unstable" 17 "gopkg.in/mgo.v2/bson" 18 "gopkg.in/mgo.v2/txn" 19 20 "github.com/juju/juju/constraints" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/state/testing" 23 "github.com/juju/juju/status" 24 coretesting "github.com/juju/juju/testing" 25 "github.com/juju/juju/testing/factory" 26 ) 27 28 type ApplicationSuite struct { 29 ConnSuite 30 charm *state.Charm 31 mysql *state.Application 32 } 33 34 var _ = gc.Suite(&ApplicationSuite{}) 35 36 func (s *ApplicationSuite) SetUpTest(c *gc.C) { 37 s.ConnSuite.SetUpTest(c) 38 s.policy.GetConstraintsValidator = func() (constraints.Validator, error) { 39 validator := constraints.NewValidator() 40 validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) 41 validator.RegisterUnsupported([]string{constraints.CpuPower}) 42 return validator, nil 43 } 44 s.charm = s.AddTestingCharm(c, "mysql") 45 s.mysql = s.AddTestingService(c, "mysql", s.charm) 46 } 47 48 func (s *ApplicationSuite) TestSetCharm(c *gc.C) { 49 ch, force, err := s.mysql.Charm() 50 c.Assert(err, jc.ErrorIsNil) 51 c.Assert(ch.URL(), gc.DeepEquals, s.charm.URL()) 52 c.Assert(force, jc.IsFalse) 53 url, force := s.mysql.CharmURL() 54 c.Assert(url, gc.DeepEquals, s.charm.URL()) 55 c.Assert(force, jc.IsFalse) 56 57 // Add a compatible charm and force it. 58 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 59 60 cfg := state.SetCharmConfig{ 61 Charm: sch, 62 ForceUnits: true, 63 } 64 err = s.mysql.SetCharm(cfg) 65 c.Assert(err, jc.ErrorIsNil) 66 ch, force, err = s.mysql.Charm() 67 c.Assert(err, jc.ErrorIsNil) 68 c.Assert(ch.URL(), gc.DeepEquals, sch.URL()) 69 c.Assert(force, jc.IsTrue) 70 url, force = s.mysql.CharmURL() 71 c.Assert(url, gc.DeepEquals, sch.URL()) 72 c.Assert(force, jc.IsTrue) 73 } 74 75 func (s *ApplicationSuite) TestSetCharmCharmSettings(c *gc.C) { 76 newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2) 77 err := s.mysql.SetCharm(state.SetCharmConfig{ 78 Charm: newCh, 79 ConfigSettings: charm.Settings{"key": "value"}, 80 }) 81 c.Assert(err, jc.ErrorIsNil) 82 83 settings, err := s.mysql.ConfigSettings() 84 c.Assert(err, jc.ErrorIsNil) 85 c.Assert(settings, jc.DeepEquals, charm.Settings{"key": "value"}) 86 87 newCh = s.AddConfigCharm(c, "mysql", newStringConfig, 3) 88 err = s.mysql.SetCharm(state.SetCharmConfig{ 89 Charm: newCh, 90 ConfigSettings: charm.Settings{"other": "one"}, 91 }) 92 c.Assert(err, jc.ErrorIsNil) 93 94 settings, err = s.mysql.ConfigSettings() 95 c.Assert(err, jc.ErrorIsNil) 96 c.Assert(settings, jc.DeepEquals, charm.Settings{ 97 "key": "value", 98 "other": "one", 99 }) 100 } 101 102 func (s *ApplicationSuite) TestSetCharmCharmSettingsInvalid(c *gc.C) { 103 newCh := s.AddConfigCharm(c, "mysql", stringConfig, 2) 104 err := s.mysql.SetCharm(state.SetCharmConfig{ 105 Charm: newCh, 106 ConfigSettings: charm.Settings{"key": 123.45}, 107 }) 108 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-mysql-2": validating config settings: option "key" expected string, got 123.45`) 109 } 110 111 func (s *ApplicationSuite) TestSetCharmLegacy(c *gc.C) { 112 chDifferentSeries := state.AddTestingCharmForSeries(c, s.State, "precise", "mysql") 113 114 cfg := state.SetCharmConfig{ 115 Charm: chDifferentSeries, 116 ForceSeries: true, 117 } 118 err := s.mysql.SetCharm(cfg) 119 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:precise/precise-mysql-1": cannot change an application's series`) 120 } 121 122 func (s *ApplicationSuite) TestClientServiceSetCharmUnsupportedSeries(c *gc.C) { 123 ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series") 124 svc := state.AddTestingServiceForSeries(c, s.State, "precise", "application", ch) 125 126 chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2") 127 cfg := state.SetCharmConfig{ 128 Charm: chDifferentSeries, 129 } 130 err := svc.SetCharm(cfg) 131 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:multi-series2-8": only these series are supported: trusty, wily`) 132 } 133 134 func (s *ApplicationSuite) TestClientServiceSetCharmUnsupportedSeriesForce(c *gc.C) { 135 ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series") 136 svc := state.AddTestingServiceForSeries(c, s.State, "precise", "application", ch) 137 138 chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series2") 139 cfg := state.SetCharmConfig{ 140 Charm: chDifferentSeries, 141 ForceSeries: true, 142 } 143 err := svc.SetCharm(cfg) 144 c.Assert(err, jc.ErrorIsNil) 145 svc, err = s.State.Application("application") 146 c.Assert(err, jc.ErrorIsNil) 147 ch, _, err = svc.Charm() 148 c.Assert(err, jc.ErrorIsNil) 149 c.Assert(ch.URL().String(), gc.Equals, "cs:multi-series2-8") 150 } 151 152 func (s *ApplicationSuite) TestClientServiceSetCharmWrongOS(c *gc.C) { 153 ch := state.AddTestingCharmMultiSeries(c, s.State, "multi-series") 154 svc := state.AddTestingServiceForSeries(c, s.State, "precise", "application", ch) 155 156 chDifferentSeries := state.AddTestingCharmMultiSeries(c, s.State, "multi-series-windows") 157 cfg := state.SetCharmConfig{ 158 Charm: chDifferentSeries, 159 ForceSeries: true, 160 } 161 err := svc.SetCharm(cfg) 162 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "application" to charm "cs:multi-series-windows-1": OS "Ubuntu" not supported by charm`) 163 } 164 165 func (s *ApplicationSuite) TestSetCharmPreconditions(c *gc.C) { 166 logging := s.AddTestingCharm(c, "logging") 167 cfg := state.SetCharmConfig{Charm: logging} 168 err := s.mysql.SetCharm(cfg) 169 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:quantal/quantal-logging-1": cannot change an application's subordinacy`) 170 171 othermysql := s.AddSeriesCharm(c, "mysql", "otherseries") 172 cfg2 := state.SetCharmConfig{Charm: othermysql} 173 err = s.mysql.SetCharm(cfg2) 174 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "mysql" to charm "local:otherseries/otherseries-mysql-1": cannot change an application's series`) 175 } 176 177 func (s *ApplicationSuite) TestSetCharmUpdatesBindings(c *gc.C) { 178 _, err := s.State.AddSpace("db", "", nil, false) 179 c.Assert(err, jc.ErrorIsNil) 180 _, err = s.State.AddSpace("client", "", nil, true) 181 c.Assert(err, jc.ErrorIsNil) 182 oldCharm := s.AddMetaCharm(c, "mysql", metaBase, 44) 183 184 service, err := s.State.AddApplication(state.AddApplicationArgs{ 185 Name: "yoursql", 186 Charm: oldCharm, 187 EndpointBindings: map[string]string{ 188 "server": "db", 189 "client": "client", 190 }}) 191 c.Assert(err, jc.ErrorIsNil) 192 193 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43) 194 cfg := state.SetCharmConfig{Charm: newCharm} 195 err = service.SetCharm(cfg) 196 c.Assert(err, jc.ErrorIsNil) 197 updatedBindings, err := service.EndpointBindings() 198 c.Assert(err, jc.ErrorIsNil) 199 c.Assert(updatedBindings, jc.DeepEquals, map[string]string{ 200 // Existing bindings are preserved. 201 "server": "db", 202 "client": "client", 203 "cluster": "", // inherited from defaults in AddService. 204 // New endpoints use empty defaults. 205 "foo": "", 206 "baz": "", 207 "just": "", 208 }) 209 } 210 211 func (s *ApplicationSuite) TestSetCharmWithWeirdlyNamedEndpoints(c *gc.C) { 212 // This test ensures if special characters appear in endpoint names of the 213 // charm metadata, they are properly escaped before saving to mongo, and 214 // unescaped when read back. 215 _, err := s.State.AddSpace("client", "", nil, true) 216 c.Assert(err, jc.ErrorIsNil) 217 _, err = s.State.AddSpace("db", "", nil, false) 218 c.Assert(err, jc.ErrorIsNil) 219 220 initialBindings := map[string]string{ 221 "$pull": "db", 222 "$set.foo": "", 223 "cli ent .": "client", 224 ".": "db", 225 } 226 weirdOldCharm := s.AddMetaCharm(c, "mysql", ` 227 name: mysql 228 summary: "Fake MySQL Database engine" 229 description: "Complete with nonsense relations" 230 provides: 231 $pull: mysql 232 $set.foo: bar 233 requires: 234 foo: something 235 "cli ent .": mysql 236 peers: 237 ".": bad 238 "$": mysql 239 `, 42) 240 weirdNewCharm := s.AddMetaCharm(c, "mysql", ` 241 name: mysql 242 summary: "Fake MySQL Database engine" 243 description: "Complete with nonsense relations" 244 provides: 245 ser$ver2: mysql 246 $pull: mysql 247 $set.foo: bar 248 requires: 249 "cli ent 2": mysql 250 peers: 251 "$": mysql 252 ".": bad 253 `, 43) 254 255 weirdService := s.AddTestingServiceWithBindings(c, "weird", weirdOldCharm, initialBindings) 256 readBindings, err := weirdService.EndpointBindings() 257 c.Assert(err, jc.ErrorIsNil) 258 expectedBindings := map[string]string{ 259 "cli ent .": "client", 260 "foo": "", 261 "$": "", 262 ".": "db", 263 "$set.foo": "", 264 "$pull": "db", 265 } 266 c.Check(readBindings, jc.DeepEquals, expectedBindings) 267 268 cfg := state.SetCharmConfig{Charm: weirdNewCharm} 269 err = weirdService.SetCharm(cfg) 270 c.Assert(err, jc.ErrorIsNil) 271 readBindings, err = weirdService.EndpointBindings() 272 c.Assert(err, jc.ErrorIsNil) 273 274 expectedBindings = map[string]string{ 275 "ser$ver2": "", 276 "cli ent 2": "", 277 "$": "", 278 ".": "db", 279 "$set.foo": "", 280 "$pull": "db", 281 } 282 c.Check(readBindings, jc.DeepEquals, expectedBindings) 283 } 284 285 var metaBase = ` 286 name: mysql 287 summary: "Fake MySQL Database engine" 288 description: "Complete with nonsense relations" 289 provides: 290 server: mysql 291 requires: 292 client: mysql 293 peers: 294 cluster: mysql 295 ` 296 var metaDifferentProvider = ` 297 name: mysql 298 description: none 299 summary: none 300 provides: 301 kludge: mysql 302 requires: 303 client: mysql 304 peers: 305 cluster: mysql 306 ` 307 var metaDifferentRequirer = ` 308 name: mysql 309 description: none 310 summary: none 311 provides: 312 server: mysql 313 requires: 314 kludge: mysql 315 peers: 316 cluster: mysql 317 ` 318 var metaDifferentPeer = ` 319 name: mysql 320 description: none 321 summary: none 322 provides: 323 server: mysql 324 requires: 325 client: mysql 326 peers: 327 kludge: mysql 328 ` 329 var metaExtraEndpoints = ` 330 name: mysql 331 description: none 332 summary: none 333 provides: 334 server: mysql 335 foo: bar 336 requires: 337 client: mysql 338 baz: woot 339 peers: 340 cluster: mysql 341 just: me 342 ` 343 344 var setCharmEndpointsTests = []struct { 345 summary string 346 meta string 347 err string 348 }{{ 349 summary: "different provider (but no relation yet)", 350 meta: metaDifferentProvider, 351 }, { 352 summary: "different requirer (but no relation yet)", 353 meta: metaDifferentRequirer, 354 }, { 355 summary: "different peer", 356 meta: metaDifferentPeer, 357 err: `cannot upgrade application "fakemysql" to charm "local:quantal/quantal-mysql-5": would break relation "fakemysql:cluster"`, 358 }, { 359 summary: "same relations ok", 360 meta: metaBase, 361 }, { 362 summary: "extra endpoints ok", 363 meta: metaExtraEndpoints, 364 }} 365 366 func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithoutRelations(c *gc.C) { 367 revno := 2 368 ms := s.AddMetaCharm(c, "mysql", metaBase, revno) 369 svc := s.AddTestingService(c, "fakemysql", ms) 370 cfg := state.SetCharmConfig{Charm: ms} 371 err := svc.SetCharm(cfg) 372 c.Assert(err, jc.ErrorIsNil) 373 374 for i, t := range setCharmEndpointsTests { 375 c.Logf("test %d: %s", i, t.summary) 376 377 newCh := s.AddMetaCharm(c, "mysql", t.meta, revno+i+1) 378 cfg := state.SetCharmConfig{Charm: newCh} 379 err = svc.SetCharm(cfg) 380 if t.err != "" { 381 c.Assert(err, gc.ErrorMatches, t.err) 382 } else { 383 c.Assert(err, jc.ErrorIsNil) 384 } 385 } 386 387 err = svc.Destroy() 388 c.Assert(err, jc.ErrorIsNil) 389 } 390 391 func (s *ApplicationSuite) TestSetCharmChecksEndpointsWithRelations(c *gc.C) { 392 revno := 2 393 providerCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, revno) 394 providerSvc := s.AddTestingService(c, "myprovider", providerCharm) 395 396 cfg := state.SetCharmConfig{Charm: providerCharm} 397 err := providerSvc.SetCharm(cfg) 398 c.Assert(err, jc.ErrorIsNil) 399 400 revno++ 401 requirerCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno) 402 requirerSvc := s.AddTestingService(c, "myrequirer", requirerCharm) 403 cfg = state.SetCharmConfig{Charm: requirerCharm} 404 err = requirerSvc.SetCharm(cfg) 405 c.Assert(err, jc.ErrorIsNil) 406 407 eps, err := s.State.InferEndpoints("myprovider:kludge", "myrequirer:kludge") 408 c.Assert(err, jc.ErrorIsNil) 409 _, err = s.State.AddRelation(eps...) 410 c.Assert(err, jc.ErrorIsNil) 411 412 revno++ 413 baseCharm := s.AddMetaCharm(c, "mysql", metaBase, revno) 414 cfg = state.SetCharmConfig{Charm: baseCharm} 415 err = providerSvc.SetCharm(cfg) 416 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myprovider" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`) 417 err = requirerSvc.SetCharm(cfg) 418 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "myrequirer" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`) 419 } 420 421 var stringConfig = ` 422 options: 423 key: {default: My Key, description: Desc, type: string} 424 ` 425 var emptyConfig = ` 426 options: {} 427 ` 428 var floatConfig = ` 429 options: 430 key: {default: 0.42, description: Float key, type: float} 431 ` 432 var newStringConfig = ` 433 options: 434 key: {default: My Key, description: Desc, type: string} 435 other: {default: None, description: My Other, type: string} 436 ` 437 438 var setCharmConfigTests = []struct { 439 summary string 440 startconfig string 441 startvalues charm.Settings 442 endconfig string 443 endvalues charm.Settings 444 err string 445 }{{ 446 summary: "add float key to empty config", 447 startconfig: emptyConfig, 448 endconfig: floatConfig, 449 }, { 450 summary: "add string key to empty config", 451 startconfig: emptyConfig, 452 endconfig: stringConfig, 453 }, { 454 summary: "add string key and preserve existing values", 455 startconfig: stringConfig, 456 startvalues: charm.Settings{"key": "foo"}, 457 endconfig: newStringConfig, 458 endvalues: charm.Settings{"key": "foo"}, 459 }, { 460 summary: "remove string key", 461 startconfig: stringConfig, 462 startvalues: charm.Settings{"key": "value"}, 463 endconfig: emptyConfig, 464 }, { 465 summary: "remove float key", 466 startconfig: floatConfig, 467 startvalues: charm.Settings{"key": 123.45}, 468 endconfig: emptyConfig, 469 }, { 470 summary: "change key type without values", 471 startconfig: stringConfig, 472 endconfig: floatConfig, 473 }, { 474 summary: "change key type with values", 475 startconfig: stringConfig, 476 startvalues: charm.Settings{"key": "value"}, 477 endconfig: floatConfig, 478 }} 479 480 func (s *ApplicationSuite) TestSetCharmConfig(c *gc.C) { 481 charms := map[string]*state.Charm{ 482 stringConfig: s.AddConfigCharm(c, "wordpress", stringConfig, 1), 483 emptyConfig: s.AddConfigCharm(c, "wordpress", emptyConfig, 2), 484 floatConfig: s.AddConfigCharm(c, "wordpress", floatConfig, 3), 485 newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4), 486 } 487 488 for i, t := range setCharmConfigTests { 489 c.Logf("test %d: %s", i, t.summary) 490 491 origCh := charms[t.startconfig] 492 svc := s.AddTestingService(c, "wordpress", origCh) 493 err := svc.UpdateConfigSettings(t.startvalues) 494 c.Assert(err, jc.ErrorIsNil) 495 496 newCh := charms[t.endconfig] 497 cfg := state.SetCharmConfig{Charm: newCh} 498 err = svc.SetCharm(cfg) 499 var expectVals charm.Settings 500 var expectCh *state.Charm 501 if t.err != "" { 502 c.Assert(err, gc.ErrorMatches, t.err) 503 expectCh = origCh 504 expectVals = t.startvalues 505 } else { 506 c.Assert(err, jc.ErrorIsNil) 507 expectCh = newCh 508 expectVals = t.endvalues 509 } 510 511 sch, _, err := svc.Charm() 512 c.Assert(err, jc.ErrorIsNil) 513 c.Assert(sch.URL(), gc.DeepEquals, expectCh.URL()) 514 settings, err := svc.ConfigSettings() 515 c.Assert(err, jc.ErrorIsNil) 516 if len(expectVals) == 0 { 517 c.Assert(settings, gc.HasLen, 0) 518 } else { 519 c.Assert(settings, gc.DeepEquals, expectVals) 520 } 521 522 err = svc.Destroy() 523 c.Assert(err, jc.ErrorIsNil) 524 } 525 } 526 527 func (s *ApplicationSuite) TestSetCharmWithDyingService(c *gc.C) { 528 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 529 530 _, err := s.mysql.AddUnit() 531 c.Assert(err, jc.ErrorIsNil) 532 err = s.mysql.Destroy() 533 c.Assert(err, jc.ErrorIsNil) 534 assertLife(c, s.mysql, state.Dying) 535 cfg := state.SetCharmConfig{ 536 Charm: sch, 537 ForceUnits: true, 538 } 539 err = s.mysql.SetCharm(cfg) 540 c.Assert(err, jc.ErrorIsNil) 541 } 542 543 func (s *ApplicationSuite) TestSequenceUnitIdsAfterDestroy(c *gc.C) { 544 unit, err := s.mysql.AddUnit() 545 c.Assert(err, jc.ErrorIsNil) 546 c.Assert(unit.Name(), gc.Equals, "mysql/0") 547 err = unit.Destroy() 548 c.Assert(err, jc.ErrorIsNil) 549 err = s.mysql.Destroy() 550 c.Assert(err, jc.ErrorIsNil) 551 s.mysql = s.AddTestingService(c, "mysql", s.charm) 552 unit, err = s.mysql.AddUnit() 553 c.Assert(err, jc.ErrorIsNil) 554 c.Assert(unit.Name(), gc.Equals, "mysql/1") 555 } 556 557 func (s *ApplicationSuite) TestSequenceUnitIds(c *gc.C) { 558 unit, err := s.mysql.AddUnit() 559 c.Assert(err, jc.ErrorIsNil) 560 c.Assert(unit.Name(), gc.Equals, "mysql/0") 561 unit, err = s.mysql.AddUnit() 562 c.Assert(err, jc.ErrorIsNil) 563 c.Assert(unit.Name(), gc.Equals, "mysql/1") 564 } 565 566 func (s *ApplicationSuite) TestSetCharmWhenDead(c *gc.C) { 567 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 568 569 defer state.SetBeforeHooks(c, s.State, func() { 570 _, err := s.mysql.AddUnit() 571 err = s.mysql.Destroy() 572 c.Assert(err, jc.ErrorIsNil) 573 assertLife(c, s.mysql, state.Dying) 574 575 // Change the service life to Dead manually, as there's no 576 // direct way of doing that otherwise. 577 ops := []txn.Op{{ 578 C: state.ApplicationsC, 579 Id: state.DocID(s.State, s.mysql.Name()), 580 Update: bson.D{{"$set", bson.D{{"life", state.Dead}}}}, 581 }} 582 583 err = state.RunTransaction(s.State, ops) 584 c.Assert(err, jc.ErrorIsNil) 585 assertLife(c, s.mysql, state.Dead) 586 }).Check() 587 588 cfg := state.SetCharmConfig{ 589 Charm: sch, 590 ForceUnits: true, 591 } 592 err := s.mysql.SetCharm(cfg) 593 c.Assert(errors.Cause(err), gc.Equals, state.ErrDead) 594 } 595 596 func (s *ApplicationSuite) TestSetCharmWithRemovedService(c *gc.C) { 597 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 598 599 err := s.mysql.Destroy() 600 c.Assert(err, jc.ErrorIsNil) 601 assertRemoved(c, s.mysql) 602 603 cfg := state.SetCharmConfig{ 604 Charm: sch, 605 ForceUnits: true, 606 } 607 608 err = s.mysql.SetCharm(cfg) 609 c.Assert(err, jc.Satisfies, errors.IsNotFound) 610 } 611 612 func (s *ApplicationSuite) TestSetCharmWhenRemoved(c *gc.C) { 613 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 614 615 defer state.SetBeforeHooks(c, s.State, func() { 616 err := s.mysql.Destroy() 617 c.Assert(err, jc.ErrorIsNil) 618 assertRemoved(c, s.mysql) 619 }).Check() 620 621 cfg := state.SetCharmConfig{ 622 Charm: sch, 623 ForceUnits: true, 624 } 625 err := s.mysql.SetCharm(cfg) 626 c.Assert(err, jc.Satisfies, errors.IsNotFound) 627 } 628 629 func (s *ApplicationSuite) TestSetCharmWhenDyingIsOK(c *gc.C) { 630 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 631 632 defer state.SetBeforeHooks(c, s.State, func() { 633 _, err := s.mysql.AddUnit() 634 c.Assert(err, jc.ErrorIsNil) 635 err = s.mysql.Destroy() 636 c.Assert(err, jc.ErrorIsNil) 637 assertLife(c, s.mysql, state.Dying) 638 }).Check() 639 640 cfg := state.SetCharmConfig{ 641 Charm: sch, 642 ForceUnits: true, 643 } 644 err := s.mysql.SetCharm(cfg) 645 c.Assert(err, jc.ErrorIsNil) 646 assertLife(c, s.mysql, state.Dying) 647 } 648 649 func (s *ApplicationSuite) TestSetCharmRetriesWithSameCharmURL(c *gc.C) { 650 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) 651 652 defer state.SetTestHooks(c, s.State, 653 jujutxn.TestHook{ 654 Before: func() { 655 currentCh, force, err := s.mysql.Charm() 656 c.Assert(err, jc.ErrorIsNil) 657 c.Assert(force, jc.IsFalse) 658 c.Assert(currentCh.URL(), jc.DeepEquals, s.charm.URL()) 659 660 cfg := state.SetCharmConfig{Charm: sch} 661 err = s.mysql.SetCharm(cfg) 662 c.Assert(err, jc.ErrorIsNil) 663 }, 664 After: func() { 665 // Verify the before hook worked. 666 currentCh, force, err := s.mysql.Charm() 667 c.Assert(err, jc.ErrorIsNil) 668 c.Assert(force, jc.IsFalse) 669 c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL()) 670 }, 671 }, 672 jujutxn.TestHook{ 673 Before: nil, // Ensure there will be a retry. 674 After: func() { 675 // Verify it worked after the retry. 676 err := s.mysql.Refresh() 677 c.Assert(err, jc.ErrorIsNil) 678 currentCh, force, err := s.mysql.Charm() 679 c.Assert(err, jc.ErrorIsNil) 680 c.Assert(force, jc.IsTrue) 681 c.Assert(currentCh.URL(), jc.DeepEquals, sch.URL()) 682 }, 683 }, 684 ).Check() 685 686 cfg := state.SetCharmConfig{ 687 Charm: sch, 688 ForceUnits: true, 689 } 690 err := s.mysql.SetCharm(cfg) 691 c.Assert(err, jc.ErrorIsNil) 692 } 693 694 func (s *ApplicationSuite) TestSetCharmRetriesWhenOldSettingsChanged(c *gc.C) { 695 revno := 2 // revno 1 is used by SetUpSuite 696 oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno) 697 newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1) 698 cfg := state.SetCharmConfig{Charm: oldCh} 699 err := s.mysql.SetCharm(cfg) 700 c.Assert(err, jc.ErrorIsNil) 701 702 defer state.SetBeforeHooks(c, s.State, 703 func() { 704 err := s.mysql.UpdateConfigSettings(charm.Settings{"key": "value"}) 705 c.Assert(err, jc.ErrorIsNil) 706 }, 707 nil, // Ensure there will be a retry. 708 ).Check() 709 710 cfg = state.SetCharmConfig{ 711 Charm: newCh, 712 ForceUnits: true, 713 } 714 err = s.mysql.SetCharm(cfg) 715 c.Assert(err, jc.ErrorIsNil) 716 } 717 718 func (s *ApplicationSuite) TestSetCharmRetriesWhenBothOldAndNewSettingsChanged(c *gc.C) { 719 revno := 2 // revno 1 is used by SetUpSuite 720 oldCh := s.AddConfigCharm(c, "mysql", stringConfig, revno) 721 newCh := s.AddConfigCharm(c, "mysql", stringConfig, revno+1) 722 723 defer state.SetTestHooks(c, s.State, 724 jujutxn.TestHook{ 725 Before: func() { 726 // Add two units, which will keep the refcount of oldCh 727 // and newCh settings greater than 0, while the service's 728 // charm URLs change between oldCh and newCh. Ensure 729 // refcounts change as expected. 730 unit1, err := s.mysql.AddUnit() 731 c.Assert(err, jc.ErrorIsNil) 732 unit2, err := s.mysql.AddUnit() 733 c.Assert(err, jc.ErrorIsNil) 734 cfg := state.SetCharmConfig{Charm: newCh} 735 err = s.mysql.SetCharm(cfg) 736 c.Assert(err, jc.ErrorIsNil) 737 assertSettingsRef(c, s.State, "mysql", newCh, 1) 738 assertNoSettingsRef(c, s.State, "mysql", oldCh) 739 err = unit1.SetCharmURL(newCh.URL()) 740 c.Assert(err, jc.ErrorIsNil) 741 assertSettingsRef(c, s.State, "mysql", newCh, 2) 742 assertNoSettingsRef(c, s.State, "mysql", oldCh) 743 // Update newCh settings, switch to oldCh and update its 744 // settings as well. 745 err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value1"}) 746 c.Assert(err, jc.ErrorIsNil) 747 cfg = state.SetCharmConfig{Charm: oldCh} 748 749 err = s.mysql.SetCharm(cfg) 750 c.Assert(err, jc.ErrorIsNil) 751 assertSettingsRef(c, s.State, "mysql", newCh, 1) 752 assertSettingsRef(c, s.State, "mysql", oldCh, 1) 753 err = unit2.SetCharmURL(oldCh.URL()) 754 c.Assert(err, jc.ErrorIsNil) 755 assertSettingsRef(c, s.State, "mysql", newCh, 1) 756 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 757 err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value2"}) 758 c.Assert(err, jc.ErrorIsNil) 759 }, 760 After: func() { 761 // Verify the charm and refcounts after the second attempt. 762 err := s.mysql.Refresh() 763 c.Assert(err, jc.ErrorIsNil) 764 currentCh, force, err := s.mysql.Charm() 765 c.Assert(err, jc.ErrorIsNil) 766 c.Assert(force, jc.IsFalse) 767 c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL()) 768 assertSettingsRef(c, s.State, "mysql", newCh, 1) 769 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 770 }, 771 }, 772 jujutxn.TestHook{ 773 Before: func() { 774 // SetCharm has refreshed its cached settings for oldCh 775 // and newCh. Change them again to trigger another 776 // attempt. 777 cfg := state.SetCharmConfig{Charm: newCh} 778 779 err := s.mysql.SetCharm(cfg) 780 c.Assert(err, jc.ErrorIsNil) 781 assertSettingsRef(c, s.State, "mysql", newCh, 2) 782 assertSettingsRef(c, s.State, "mysql", oldCh, 1) 783 err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value3"}) 784 c.Assert(err, jc.ErrorIsNil) 785 786 cfg = state.SetCharmConfig{Charm: oldCh} 787 err = s.mysql.SetCharm(cfg) 788 c.Assert(err, jc.ErrorIsNil) 789 assertSettingsRef(c, s.State, "mysql", newCh, 1) 790 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 791 err = s.mysql.UpdateConfigSettings(charm.Settings{"key": "value4"}) 792 c.Assert(err, jc.ErrorIsNil) 793 }, 794 After: func() { 795 // Verify the charm and refcounts after the third attempt. 796 err := s.mysql.Refresh() 797 c.Assert(err, jc.ErrorIsNil) 798 currentCh, force, err := s.mysql.Charm() 799 c.Assert(err, jc.ErrorIsNil) 800 c.Assert(force, jc.IsFalse) 801 c.Assert(currentCh.URL(), jc.DeepEquals, oldCh.URL()) 802 assertSettingsRef(c, s.State, "mysql", newCh, 1) 803 assertSettingsRef(c, s.State, "mysql", oldCh, 2) 804 }, 805 }, 806 jujutxn.TestHook{ 807 Before: nil, // Ensure there will be a (final) retry. 808 After: func() { 809 // Verify the charm and refcounts after the final third attempt. 810 err := s.mysql.Refresh() 811 c.Assert(err, jc.ErrorIsNil) 812 currentCh, force, err := s.mysql.Charm() 813 c.Assert(err, jc.ErrorIsNil) 814 c.Assert(force, jc.IsTrue) 815 c.Assert(currentCh.URL(), jc.DeepEquals, newCh.URL()) 816 assertSettingsRef(c, s.State, "mysql", newCh, 2) 817 assertSettingsRef(c, s.State, "mysql", oldCh, 1) 818 }, 819 }, 820 ).Check() 821 822 cfg := state.SetCharmConfig{ 823 Charm: newCh, 824 ForceUnits: true, 825 } 826 err := s.mysql.SetCharm(cfg) 827 c.Assert(err, jc.ErrorIsNil) 828 } 829 830 func (s *ApplicationSuite) TestSetCharmRetriesWhenOldBindingsChanged(c *gc.C) { 831 revno := 2 // revno 1 is used by SetUpSuite 832 mysqlKey := state.ApplicationGlobalKey(s.mysql.Name()) 833 oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno) 834 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, revno+1) 835 836 cfg := state.SetCharmConfig{Charm: oldCharm} 837 err := s.mysql.SetCharm(cfg) 838 c.Assert(err, jc.ErrorIsNil) 839 840 oldBindings, err := s.mysql.EndpointBindings() 841 c.Assert(err, jc.ErrorIsNil) 842 c.Assert(oldBindings, jc.DeepEquals, map[string]string{ 843 "server": "", 844 "kludge": "", 845 "cluster": "", 846 }) 847 _, err = s.State.AddSpace("db", "", nil, true) 848 c.Assert(err, jc.ErrorIsNil) 849 _, err = s.State.AddSpace("admin", "", nil, false) 850 c.Assert(err, jc.ErrorIsNil) 851 852 updateBindings := func(updatesMap bson.M) { 853 ops := []txn.Op{{ 854 C: state.EndpointBindingsC, 855 Id: mysqlKey, 856 Update: bson.D{{"$set", updatesMap}}, 857 }} 858 err := state.RunTransaction(s.State, ops) 859 c.Assert(err, jc.ErrorIsNil) 860 } 861 862 defer state.SetTestHooks(c, s.State, 863 jujutxn.TestHook{ 864 Before: func() { 865 // First change. 866 updateBindings(bson.M{ 867 "bindings.server": "db", 868 "bindings.kludge": "admin", // will be removed before newCharm is set. 869 }) 870 }, 871 After: func() { 872 // Second change. 873 updateBindings(bson.M{ 874 "bindings.cluster": "admin", 875 }) 876 }, 877 }, 878 jujutxn.TestHook{ 879 Before: nil, // Ensure there will be a (final) retry. 880 After: func() { 881 // Verify final bindings. 882 newBindings, err := s.mysql.EndpointBindings() 883 c.Assert(err, jc.ErrorIsNil) 884 c.Assert(newBindings, jc.DeepEquals, map[string]string{ 885 "server": "db", // from the first change. 886 "foo": "", 887 "client": "", 888 "baz": "", 889 "cluster": "admin", // from the second change. 890 "just": "", 891 }) 892 }, 893 }, 894 ).Check() 895 896 cfg = state.SetCharmConfig{ 897 Charm: newCharm, 898 ForceUnits: true, 899 } 900 err = s.mysql.SetCharm(cfg) 901 c.Assert(err, jc.ErrorIsNil) 902 } 903 904 var serviceUpdateConfigSettingsTests = []struct { 905 about string 906 initial charm.Settings 907 update charm.Settings 908 expect charm.Settings 909 err string 910 }{{ 911 about: "unknown option", 912 update: charm.Settings{"foo": "bar"}, 913 err: `unknown option "foo"`, 914 }, { 915 about: "bad type", 916 update: charm.Settings{"skill-level": "profound"}, 917 err: `option "skill-level" expected int, got "profound"`, 918 }, { 919 about: "set string", 920 update: charm.Settings{"outlook": "positive"}, 921 expect: charm.Settings{"outlook": "positive"}, 922 }, { 923 about: "unset string and set another", 924 initial: charm.Settings{"outlook": "positive"}, 925 update: charm.Settings{"outlook": nil, "title": "sir"}, 926 expect: charm.Settings{"title": "sir"}, 927 }, { 928 about: "unset missing string", 929 update: charm.Settings{"outlook": nil}, 930 }, { 931 about: `empty strings are valid`, 932 initial: charm.Settings{"outlook": "positive"}, 933 update: charm.Settings{"outlook": "", "title": ""}, 934 expect: charm.Settings{"outlook": "", "title": ""}, 935 }, { 936 about: "preserve existing value", 937 initial: charm.Settings{"title": "sir"}, 938 update: charm.Settings{"username": "admin001"}, 939 expect: charm.Settings{"username": "admin001", "title": "sir"}, 940 }, { 941 about: "unset a default value, set a different default", 942 initial: charm.Settings{"username": "admin001", "title": "sir"}, 943 update: charm.Settings{"username": nil, "title": "My Title"}, 944 expect: charm.Settings{"title": "My Title"}, 945 }, { 946 about: "non-string type", 947 update: charm.Settings{"skill-level": 303}, 948 expect: charm.Settings{"skill-level": int64(303)}, 949 }, { 950 about: "unset non-string type", 951 initial: charm.Settings{"skill-level": 303}, 952 update: charm.Settings{"skill-level": nil}, 953 }} 954 955 func (s *ApplicationSuite) TestUpdateConfigSettings(c *gc.C) { 956 sch := s.AddTestingCharm(c, "dummy") 957 for i, t := range serviceUpdateConfigSettingsTests { 958 c.Logf("test %d. %s", i, t.about) 959 svc := s.AddTestingService(c, "dummy-application", sch) 960 if t.initial != nil { 961 err := svc.UpdateConfigSettings(t.initial) 962 c.Assert(err, jc.ErrorIsNil) 963 } 964 err := svc.UpdateConfigSettings(t.update) 965 if t.err != "" { 966 c.Assert(err, gc.ErrorMatches, t.err) 967 } else { 968 c.Assert(err, jc.ErrorIsNil) 969 settings, err := svc.ConfigSettings() 970 c.Assert(err, jc.ErrorIsNil) 971 expect := t.expect 972 if expect == nil { 973 expect = charm.Settings{} 974 } 975 c.Assert(settings, gc.DeepEquals, expect) 976 } 977 err = svc.Destroy() 978 c.Assert(err, jc.ErrorIsNil) 979 } 980 } 981 982 func assertNoSettingsRef(c *gc.C, st *state.State, svcName string, sch *state.Charm) { 983 _, err := state.ServiceSettingsRefCount(st, svcName, sch.URL()) 984 c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound) 985 } 986 987 func assertSettingsRef(c *gc.C, st *state.State, svcName string, sch *state.Charm, refcount int) { 988 rc, err := state.ServiceSettingsRefCount(st, svcName, sch.URL()) 989 c.Assert(err, jc.ErrorIsNil) 990 c.Assert(rc, gc.Equals, refcount) 991 } 992 993 func (s *ApplicationSuite) TestSettingsRefCountWorks(c *gc.C) { 994 // This test ensures the service settings per charm URL are 995 // properly reference counted. 996 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 997 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 998 svcName := "mywp" 999 1000 // Both refcounts are zero initially. 1001 assertNoSettingsRef(c, s.State, svcName, oldCh) 1002 assertNoSettingsRef(c, s.State, svcName, newCh) 1003 1004 // svc is using oldCh, so its settings refcount is incremented. 1005 svc := s.AddTestingService(c, svcName, oldCh) 1006 assertSettingsRef(c, s.State, svcName, oldCh, 1) 1007 assertNoSettingsRef(c, s.State, svcName, newCh) 1008 1009 // Changing to the same charm does not change the refcount. 1010 cfg := state.SetCharmConfig{Charm: oldCh} 1011 err := svc.SetCharm(cfg) 1012 c.Assert(err, jc.ErrorIsNil) 1013 assertSettingsRef(c, s.State, svcName, oldCh, 1) 1014 assertNoSettingsRef(c, s.State, svcName, newCh) 1015 1016 // Changing from oldCh to newCh causes the refcount of oldCh's 1017 // settings to be decremented, while newCh's settings is 1018 // incremented. Consequently, because oldCh's refcount is 0, the 1019 // settings doc will be removed. 1020 cfg = state.SetCharmConfig{Charm: newCh} 1021 err = svc.SetCharm(cfg) 1022 c.Assert(err, jc.ErrorIsNil) 1023 assertNoSettingsRef(c, s.State, svcName, oldCh) 1024 assertSettingsRef(c, s.State, svcName, newCh, 1) 1025 1026 // The same but newCh swapped with oldCh. 1027 cfg = state.SetCharmConfig{Charm: oldCh} 1028 err = svc.SetCharm(cfg) 1029 c.Assert(err, jc.ErrorIsNil) 1030 assertSettingsRef(c, s.State, svcName, oldCh, 1) 1031 assertNoSettingsRef(c, s.State, svcName, newCh) 1032 1033 // Adding a unit without a charm URL set does not affect the 1034 // refcount. 1035 u, err := svc.AddUnit() 1036 c.Assert(err, jc.ErrorIsNil) 1037 curl, ok := u.CharmURL() 1038 c.Assert(ok, jc.IsFalse) 1039 assertSettingsRef(c, s.State, svcName, oldCh, 1) 1040 assertNoSettingsRef(c, s.State, svcName, newCh) 1041 1042 // Setting oldCh as the units charm URL increments oldCh, which is 1043 // used by svc as well, hence 2. 1044 err = u.SetCharmURL(oldCh.URL()) 1045 c.Assert(err, jc.ErrorIsNil) 1046 curl, ok = u.CharmURL() 1047 c.Assert(ok, jc.IsTrue) 1048 c.Assert(curl, gc.DeepEquals, oldCh.URL()) 1049 assertSettingsRef(c, s.State, svcName, oldCh, 2) 1050 assertNoSettingsRef(c, s.State, svcName, newCh) 1051 1052 // A dead unit does not decrement the refcount. 1053 err = u.EnsureDead() 1054 c.Assert(err, jc.ErrorIsNil) 1055 assertSettingsRef(c, s.State, svcName, oldCh, 2) 1056 assertNoSettingsRef(c, s.State, svcName, newCh) 1057 1058 // Once the unit is removed, refcount is decremented. 1059 err = u.Remove() 1060 c.Assert(err, jc.ErrorIsNil) 1061 assertSettingsRef(c, s.State, svcName, oldCh, 1) 1062 assertNoSettingsRef(c, s.State, svcName, newCh) 1063 1064 // Finally, after the service is destroyed and removed (since the 1065 // last unit's gone), the refcount is again decremented. 1066 err = svc.Destroy() 1067 c.Assert(err, jc.ErrorIsNil) 1068 assertNoSettingsRef(c, s.State, svcName, oldCh) 1069 assertNoSettingsRef(c, s.State, svcName, newCh) 1070 1071 // Having studiously avoided triggering cleanups throughout, 1072 // invoke them now and check that the charms are cleaned up 1073 // correctly -- and that a storm of cleanups for the same 1074 // charm are not a problem. 1075 err = s.State.Cleanup() 1076 c.Assert(err, jc.ErrorIsNil) 1077 err = oldCh.Refresh() 1078 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1079 err = newCh.Refresh() 1080 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1081 } 1082 1083 func (s *ApplicationSuite) TestSettingsRefCreateRace(c *gc.C) { 1084 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 1085 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 1086 appName := "mywp" 1087 1088 app := s.AddTestingService(c, appName, oldCh) 1089 unit, err := app.AddUnit() 1090 c.Assert(err, jc.ErrorIsNil) 1091 1092 // just before setting the unit charm url, switch the service 1093 // away from the original charm, causing the attempt to fail 1094 // (because the settings have gone away; it's the unit's job to 1095 // fail out and handle the new charm when it comes back up 1096 // again). 1097 dropSettings := func() { 1098 cfg := state.SetCharmConfig{Charm: newCh} 1099 err = app.SetCharm(cfg) 1100 c.Assert(err, jc.ErrorIsNil) 1101 } 1102 defer state.SetBeforeHooks(c, s.State, dropSettings).Check() 1103 1104 err = unit.SetCharmURL(oldCh.URL()) 1105 c.Check(err, gc.ErrorMatches, "settings reference: does not exist") 1106 } 1107 1108 func (s *ApplicationSuite) TestSettingsRefRemoveRace(c *gc.C) { 1109 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 1110 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 1111 appName := "mywp" 1112 1113 app := s.AddTestingService(c, appName, oldCh) 1114 unit, err := app.AddUnit() 1115 c.Assert(err, jc.ErrorIsNil) 1116 1117 // just before updating the app charm url, set that charm url on 1118 // a unit to block the removal. 1119 grabReference := func() { 1120 err := unit.SetCharmURL(oldCh.URL()) 1121 c.Assert(err, jc.ErrorIsNil) 1122 } 1123 defer state.SetBeforeHooks(c, s.State, grabReference).Check() 1124 1125 cfg := state.SetCharmConfig{Charm: newCh} 1126 err = app.SetCharm(cfg) 1127 c.Assert(err, jc.ErrorIsNil) 1128 1129 // check refs to both settings exist 1130 assertSettingsRef(c, s.State, appName, oldCh, 1) 1131 assertSettingsRef(c, s.State, appName, newCh, 1) 1132 } 1133 1134 const mysqlBaseMeta = ` 1135 name: mysql 1136 summary: "Database engine" 1137 description: "A pretty popular database" 1138 provides: 1139 server: mysql 1140 ` 1141 const onePeerMeta = ` 1142 peers: 1143 cluster: mysql 1144 ` 1145 const twoPeersMeta = ` 1146 peers: 1147 cluster: mysql 1148 loadbalancer: phony 1149 ` 1150 1151 func (s *ApplicationSuite) assertApplicationRelations(c *gc.C, svc *state.Application, expectedKeys ...string) []*state.Relation { 1152 rels, err := svc.Relations() 1153 c.Assert(err, jc.ErrorIsNil) 1154 if len(rels) == 0 { 1155 return nil 1156 } 1157 relKeys := make([]string, len(expectedKeys)) 1158 for i, rel := range rels { 1159 relKeys[i] = rel.String() 1160 } 1161 sort.Strings(relKeys) 1162 c.Assert(relKeys, gc.DeepEquals, expectedKeys) 1163 return rels 1164 } 1165 1166 func (s *ApplicationSuite) TestNewPeerRelationsAddedOnUpgrade(c *gc.C) { 1167 // Original mysql charm has no peer relations. 1168 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2) 1169 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3) 1170 1171 // No relations joined yet. 1172 s.assertApplicationRelations(c, s.mysql) 1173 1174 cfg := state.SetCharmConfig{Charm: oldCh} 1175 err := s.mysql.SetCharm(cfg) 1176 c.Assert(err, jc.ErrorIsNil) 1177 s.assertApplicationRelations(c, s.mysql, "mysql:cluster") 1178 1179 cfg = state.SetCharmConfig{Charm: newCh} 1180 err = s.mysql.SetCharm(cfg) 1181 c.Assert(err, jc.ErrorIsNil) 1182 rels := s.assertApplicationRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer") 1183 1184 // Check state consistency by attempting to destroy the service. 1185 err = s.mysql.Destroy() 1186 c.Assert(err, jc.ErrorIsNil) 1187 1188 // Check the peer relations got destroyed as well. 1189 for _, rel := range rels { 1190 err = rel.Refresh() 1191 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1192 } 1193 } 1194 1195 func jujuInfoEp(applicationname string) state.Endpoint { 1196 return state.Endpoint{ 1197 ApplicationName: applicationname, 1198 Relation: charm.Relation{ 1199 Interface: "juju-info", 1200 Name: "juju-info", 1201 Role: charm.RoleProvider, 1202 Scope: charm.ScopeGlobal, 1203 }, 1204 } 1205 } 1206 1207 func (s *ApplicationSuite) TestTag(c *gc.C) { 1208 c.Assert(s.mysql.Tag().String(), gc.Equals, "application-mysql") 1209 } 1210 1211 func (s *ApplicationSuite) TestMysqlEndpoints(c *gc.C) { 1212 _, err := s.mysql.Endpoint("mysql") 1213 c.Assert(err, gc.ErrorMatches, `application "mysql" has no "mysql" relation`) 1214 1215 jiEP, err := s.mysql.Endpoint("juju-info") 1216 c.Assert(err, jc.ErrorIsNil) 1217 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("mysql")) 1218 1219 serverEP, err := s.mysql.Endpoint("server") 1220 c.Assert(err, jc.ErrorIsNil) 1221 c.Assert(serverEP, gc.DeepEquals, state.Endpoint{ 1222 ApplicationName: "mysql", 1223 Relation: charm.Relation{ 1224 Interface: "mysql", 1225 Name: "server", 1226 Role: charm.RoleProvider, 1227 Scope: charm.ScopeGlobal, 1228 }, 1229 }) 1230 1231 eps, err := s.mysql.Endpoints() 1232 c.Assert(err, jc.ErrorIsNil) 1233 c.Assert(eps, gc.DeepEquals, []state.Endpoint{jiEP, serverEP}) 1234 } 1235 1236 func (s *ApplicationSuite) TestRiakEndpoints(c *gc.C) { 1237 riak := s.AddTestingService(c, "myriak", s.AddTestingCharm(c, "riak")) 1238 1239 _, err := riak.Endpoint("garble") 1240 c.Assert(err, gc.ErrorMatches, `application "myriak" has no "garble" relation`) 1241 1242 jiEP, err := riak.Endpoint("juju-info") 1243 c.Assert(err, jc.ErrorIsNil) 1244 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("myriak")) 1245 1246 ringEP, err := riak.Endpoint("ring") 1247 c.Assert(err, jc.ErrorIsNil) 1248 c.Assert(ringEP, gc.DeepEquals, state.Endpoint{ 1249 ApplicationName: "myriak", 1250 Relation: charm.Relation{ 1251 Interface: "riak", 1252 Name: "ring", 1253 Role: charm.RolePeer, 1254 Scope: charm.ScopeGlobal, 1255 Limit: 1, 1256 }, 1257 }) 1258 1259 adminEP, err := riak.Endpoint("admin") 1260 c.Assert(err, jc.ErrorIsNil) 1261 c.Assert(adminEP, gc.DeepEquals, state.Endpoint{ 1262 ApplicationName: "myriak", 1263 Relation: charm.Relation{ 1264 Interface: "http", 1265 Name: "admin", 1266 Role: charm.RoleProvider, 1267 Scope: charm.ScopeGlobal, 1268 }, 1269 }) 1270 1271 endpointEP, err := riak.Endpoint("endpoint") 1272 c.Assert(err, jc.ErrorIsNil) 1273 c.Assert(endpointEP, gc.DeepEquals, state.Endpoint{ 1274 ApplicationName: "myriak", 1275 Relation: charm.Relation{ 1276 Interface: "http", 1277 Name: "endpoint", 1278 Role: charm.RoleProvider, 1279 Scope: charm.ScopeGlobal, 1280 }, 1281 }) 1282 1283 eps, err := riak.Endpoints() 1284 c.Assert(err, jc.ErrorIsNil) 1285 c.Assert(eps, gc.DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP}) 1286 } 1287 1288 func (s *ApplicationSuite) TestWordpressEndpoints(c *gc.C) { 1289 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1290 1291 _, err := wordpress.Endpoint("nonsense") 1292 c.Assert(err, gc.ErrorMatches, `application "wordpress" has no "nonsense" relation`) 1293 1294 jiEP, err := wordpress.Endpoint("juju-info") 1295 c.Assert(err, jc.ErrorIsNil) 1296 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("wordpress")) 1297 1298 urlEP, err := wordpress.Endpoint("url") 1299 c.Assert(err, jc.ErrorIsNil) 1300 c.Assert(urlEP, gc.DeepEquals, state.Endpoint{ 1301 ApplicationName: "wordpress", 1302 Relation: charm.Relation{ 1303 Interface: "http", 1304 Name: "url", 1305 Role: charm.RoleProvider, 1306 Scope: charm.ScopeGlobal, 1307 }, 1308 }) 1309 1310 ldEP, err := wordpress.Endpoint("logging-dir") 1311 c.Assert(err, jc.ErrorIsNil) 1312 c.Assert(ldEP, gc.DeepEquals, state.Endpoint{ 1313 ApplicationName: "wordpress", 1314 Relation: charm.Relation{ 1315 Interface: "logging", 1316 Name: "logging-dir", 1317 Role: charm.RoleProvider, 1318 Scope: charm.ScopeContainer, 1319 }, 1320 }) 1321 1322 mpEP, err := wordpress.Endpoint("monitoring-port") 1323 c.Assert(err, jc.ErrorIsNil) 1324 c.Assert(mpEP, gc.DeepEquals, state.Endpoint{ 1325 ApplicationName: "wordpress", 1326 Relation: charm.Relation{ 1327 Interface: "monitoring", 1328 Name: "monitoring-port", 1329 Role: charm.RoleProvider, 1330 Scope: charm.ScopeContainer, 1331 }, 1332 }) 1333 1334 dbEP, err := wordpress.Endpoint("db") 1335 c.Assert(err, jc.ErrorIsNil) 1336 c.Assert(dbEP, gc.DeepEquals, state.Endpoint{ 1337 ApplicationName: "wordpress", 1338 Relation: charm.Relation{ 1339 Interface: "mysql", 1340 Name: "db", 1341 Role: charm.RoleRequirer, 1342 Scope: charm.ScopeGlobal, 1343 Limit: 1, 1344 }, 1345 }) 1346 1347 cacheEP, err := wordpress.Endpoint("cache") 1348 c.Assert(err, jc.ErrorIsNil) 1349 c.Assert(cacheEP, gc.DeepEquals, state.Endpoint{ 1350 ApplicationName: "wordpress", 1351 Relation: charm.Relation{ 1352 Interface: "varnish", 1353 Name: "cache", 1354 Role: charm.RoleRequirer, 1355 Scope: charm.ScopeGlobal, 1356 Limit: 2, 1357 Optional: true, 1358 }, 1359 }) 1360 1361 eps, err := wordpress.Endpoints() 1362 c.Assert(err, jc.ErrorIsNil) 1363 c.Assert(eps, gc.DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP}) 1364 } 1365 1366 func (s *ApplicationSuite) TestServiceRefresh(c *gc.C) { 1367 s1, err := s.State.Application(s.mysql.Name()) 1368 c.Assert(err, jc.ErrorIsNil) 1369 1370 cfg := state.SetCharmConfig{ 1371 Charm: s.charm, 1372 ForceUnits: true, 1373 } 1374 1375 err = s.mysql.SetCharm(cfg) 1376 c.Assert(err, jc.ErrorIsNil) 1377 1378 testch, force, err := s1.Charm() 1379 c.Assert(err, jc.ErrorIsNil) 1380 c.Assert(force, jc.IsFalse) 1381 c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL()) 1382 1383 err = s1.Refresh() 1384 c.Assert(err, jc.ErrorIsNil) 1385 testch, force, err = s1.Charm() 1386 c.Assert(err, jc.ErrorIsNil) 1387 c.Assert(force, jc.IsTrue) 1388 c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL()) 1389 1390 err = s.mysql.Destroy() 1391 c.Assert(err, jc.ErrorIsNil) 1392 err = s.mysql.Refresh() 1393 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1394 } 1395 1396 func (s *ApplicationSuite) TestServiceExposed(c *gc.C) { 1397 // Check that querying for the exposed flag works correctly. 1398 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 1399 1400 // Check that setting and clearing the exposed flag works correctly. 1401 err := s.mysql.SetExposed() 1402 c.Assert(err, jc.ErrorIsNil) 1403 c.Assert(s.mysql.IsExposed(), jc.IsTrue) 1404 err = s.mysql.ClearExposed() 1405 c.Assert(err, jc.ErrorIsNil) 1406 c.Assert(s.mysql.IsExposed(), jc.IsFalse) 1407 1408 // Check that setting and clearing the exposed flag repeatedly does not fail. 1409 err = s.mysql.SetExposed() 1410 c.Assert(err, jc.ErrorIsNil) 1411 err = s.mysql.SetExposed() 1412 c.Assert(err, jc.ErrorIsNil) 1413 err = s.mysql.ClearExposed() 1414 c.Assert(err, jc.ErrorIsNil) 1415 err = s.mysql.ClearExposed() 1416 c.Assert(err, jc.ErrorIsNil) 1417 err = s.mysql.SetExposed() 1418 c.Assert(err, jc.ErrorIsNil) 1419 c.Assert(s.mysql.IsExposed(), jc.IsTrue) 1420 1421 // Make the service Dying and check that ClearExposed and SetExposed fail. 1422 // TODO(fwereade): maybe service destruction should always unexpose? 1423 u, err := s.mysql.AddUnit() 1424 c.Assert(err, jc.ErrorIsNil) 1425 err = s.mysql.Destroy() 1426 c.Assert(err, jc.ErrorIsNil) 1427 err = s.mysql.ClearExposed() 1428 c.Assert(err, gc.ErrorMatches, notAliveErr) 1429 err = s.mysql.SetExposed() 1430 c.Assert(err, gc.ErrorMatches, notAliveErr) 1431 1432 // Remove the service and check that both fail. 1433 err = u.EnsureDead() 1434 c.Assert(err, jc.ErrorIsNil) 1435 err = u.Remove() 1436 c.Assert(err, jc.ErrorIsNil) 1437 err = s.mysql.SetExposed() 1438 c.Assert(err, gc.ErrorMatches, notAliveErr) 1439 err = s.mysql.ClearExposed() 1440 c.Assert(err, gc.ErrorMatches, notAliveErr) 1441 } 1442 1443 func (s *ApplicationSuite) TestAddUnit(c *gc.C) { 1444 // Check that principal units can be added on their own. 1445 unitZero, err := s.mysql.AddUnit() 1446 c.Assert(err, jc.ErrorIsNil) 1447 c.Assert(unitZero.Name(), gc.Equals, "mysql/0") 1448 c.Assert(unitZero.IsPrincipal(), jc.IsTrue) 1449 c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0) 1450 c.Assert(state.GetUnitModelUUID(unitZero), gc.Equals, s.State.ModelUUID()) 1451 1452 unitOne, err := s.mysql.AddUnit() 1453 c.Assert(err, jc.ErrorIsNil) 1454 c.Assert(unitOne.Name(), gc.Equals, "mysql/1") 1455 c.Assert(unitOne.IsPrincipal(), jc.IsTrue) 1456 c.Assert(unitOne.SubordinateNames(), gc.HasLen, 0) 1457 1458 // Assign the principal unit to a machine. 1459 m, err := s.State.AddMachine("quantal", state.JobHostUnits) 1460 c.Assert(err, jc.ErrorIsNil) 1461 err = unitZero.AssignToMachine(m) 1462 c.Assert(err, jc.ErrorIsNil) 1463 1464 // Add a subordinate service and check that units cannot be added directly. 1465 // to add a subordinate unit. 1466 subCharm := s.AddTestingCharm(c, "logging") 1467 logging := s.AddTestingService(c, "logging", subCharm) 1468 _, err = logging.AddUnit() 1469 c.Assert(err, gc.ErrorMatches, `cannot add unit to application "logging": application is a subordinate`) 1470 1471 // Indirectly create a subordinate unit by adding a relation and entering 1472 // scope as a principal. 1473 eps, err := s.State.InferEndpoints("logging", "mysql") 1474 c.Assert(err, jc.ErrorIsNil) 1475 rel, err := s.State.AddRelation(eps...) 1476 c.Assert(err, jc.ErrorIsNil) 1477 ru, err := rel.Unit(unitZero) 1478 c.Assert(err, jc.ErrorIsNil) 1479 err = ru.EnterScope(nil) 1480 c.Assert(err, jc.ErrorIsNil) 1481 subZero, err := s.State.Unit("logging/0") 1482 c.Assert(err, jc.ErrorIsNil) 1483 1484 // Check that once it's refreshed unitZero has subordinates. 1485 err = unitZero.Refresh() 1486 c.Assert(err, jc.ErrorIsNil) 1487 c.Assert(unitZero.SubordinateNames(), gc.DeepEquals, []string{"logging/0"}) 1488 1489 // Check the subordinate unit has been assigned its principal's machine. 1490 id, err := subZero.AssignedMachineId() 1491 c.Assert(err, jc.ErrorIsNil) 1492 c.Assert(id, gc.Equals, m.Id()) 1493 } 1494 1495 func (s *ApplicationSuite) TestAddUnitWhenNotAlive(c *gc.C) { 1496 u, err := s.mysql.AddUnit() 1497 c.Assert(err, jc.ErrorIsNil) 1498 err = s.mysql.Destroy() 1499 c.Assert(err, jc.ErrorIsNil) 1500 _, err = s.mysql.AddUnit() 1501 c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application is not alive`) 1502 err = u.EnsureDead() 1503 c.Assert(err, jc.ErrorIsNil) 1504 err = u.Remove() 1505 c.Assert(err, jc.ErrorIsNil) 1506 _, err = s.mysql.AddUnit() 1507 c.Assert(err, gc.ErrorMatches, `cannot add unit to application "mysql": application "mysql" not found`) 1508 } 1509 1510 func (s *ApplicationSuite) TestReadUnit(c *gc.C) { 1511 _, err := s.mysql.AddUnit() 1512 c.Assert(err, jc.ErrorIsNil) 1513 _, err = s.mysql.AddUnit() 1514 c.Assert(err, jc.ErrorIsNil) 1515 1516 // Check that retrieving a unit from state works correctly. 1517 unit, err := s.State.Unit("mysql/0") 1518 c.Assert(err, jc.ErrorIsNil) 1519 c.Assert(unit.Name(), gc.Equals, "mysql/0") 1520 1521 // Check that retrieving a non-existent or an invalidly 1522 // named unit fail nicely. 1523 unit, err = s.State.Unit("mysql") 1524 c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`) 1525 unit, err = s.State.Unit("mysql/0/0") 1526 c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`) 1527 unit, err = s.State.Unit("pressword/0") 1528 c.Assert(err, gc.ErrorMatches, `unit "pressword/0" not found`) 1529 1530 units, err := s.mysql.AllUnits() 1531 c.Assert(err, jc.ErrorIsNil) 1532 c.Assert(sortedUnitNames(units), gc.DeepEquals, []string{"mysql/0", "mysql/1"}) 1533 } 1534 1535 func (s *ApplicationSuite) TestReadUnitWhenDying(c *gc.C) { 1536 // Test that we can still read units when the service is Dying... 1537 unit, err := s.mysql.AddUnit() 1538 c.Assert(err, jc.ErrorIsNil) 1539 preventUnitDestroyRemove(c, unit) 1540 err = s.mysql.Destroy() 1541 c.Assert(err, jc.ErrorIsNil) 1542 _, err = s.mysql.AllUnits() 1543 c.Assert(err, jc.ErrorIsNil) 1544 _, err = s.State.Unit("mysql/0") 1545 c.Assert(err, jc.ErrorIsNil) 1546 1547 // ...and when those units are Dying or Dead... 1548 testWhenDying(c, unit, noErr, noErr, func() error { 1549 _, err := s.mysql.AllUnits() 1550 return err 1551 }, func() error { 1552 _, err := s.State.Unit("mysql/0") 1553 return err 1554 }) 1555 1556 // ...and even, in a very limited way, when the service itself is removed. 1557 removeAllUnits(c, s.mysql) 1558 _, err = s.mysql.AllUnits() 1559 c.Assert(err, jc.ErrorIsNil) 1560 } 1561 1562 func (s *ApplicationSuite) TestDestroySimple(c *gc.C) { 1563 err := s.mysql.Destroy() 1564 c.Assert(err, jc.ErrorIsNil) 1565 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1566 err = s.mysql.Refresh() 1567 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1568 } 1569 1570 func (s *ApplicationSuite) TestDestroyStillHasUnits(c *gc.C) { 1571 unit, err := s.mysql.AddUnit() 1572 c.Assert(err, jc.ErrorIsNil) 1573 err = s.mysql.Destroy() 1574 c.Assert(err, jc.ErrorIsNil) 1575 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1576 1577 err = unit.EnsureDead() 1578 c.Assert(err, jc.ErrorIsNil) 1579 err = s.mysql.Refresh() 1580 c.Assert(err, jc.ErrorIsNil) 1581 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1582 1583 err = unit.Remove() 1584 c.Assert(err, jc.ErrorIsNil) 1585 err = s.mysql.Refresh() 1586 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1587 } 1588 1589 func (s *ApplicationSuite) TestDestroyOnceHadUnits(c *gc.C) { 1590 unit, err := s.mysql.AddUnit() 1591 c.Assert(err, jc.ErrorIsNil) 1592 err = unit.EnsureDead() 1593 c.Assert(err, jc.ErrorIsNil) 1594 err = unit.Remove() 1595 c.Assert(err, jc.ErrorIsNil) 1596 1597 err = s.mysql.Destroy() 1598 c.Assert(err, jc.ErrorIsNil) 1599 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1600 err = s.mysql.Refresh() 1601 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1602 } 1603 1604 func (s *ApplicationSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) { 1605 unit, err := s.mysql.AddUnit() 1606 c.Assert(err, jc.ErrorIsNil) 1607 err = s.mysql.Refresh() 1608 c.Assert(err, jc.ErrorIsNil) 1609 err = unit.EnsureDead() 1610 c.Assert(err, jc.ErrorIsNil) 1611 err = unit.Remove() 1612 c.Assert(err, jc.ErrorIsNil) 1613 1614 err = s.mysql.Destroy() 1615 c.Assert(err, jc.ErrorIsNil) 1616 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1617 err = s.mysql.Refresh() 1618 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1619 } 1620 1621 func (s *ApplicationSuite) TestDestroyStaleZeroUnitCount(c *gc.C) { 1622 unit, err := s.mysql.AddUnit() 1623 c.Assert(err, jc.ErrorIsNil) 1624 1625 err = s.mysql.Destroy() 1626 c.Assert(err, jc.ErrorIsNil) 1627 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1628 1629 err = s.mysql.Refresh() 1630 c.Assert(err, jc.ErrorIsNil) 1631 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1632 1633 err = unit.EnsureDead() 1634 c.Assert(err, jc.ErrorIsNil) 1635 err = s.mysql.Refresh() 1636 c.Assert(err, jc.ErrorIsNil) 1637 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 1638 1639 err = unit.Remove() 1640 c.Assert(err, jc.ErrorIsNil) 1641 err = s.mysql.Refresh() 1642 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1643 } 1644 1645 func (s *ApplicationSuite) TestDestroyWithRemovableRelation(c *gc.C) { 1646 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1647 eps, err := s.State.InferEndpoints("wordpress", "mysql") 1648 c.Assert(err, jc.ErrorIsNil) 1649 rel, err := s.State.AddRelation(eps...) 1650 c.Assert(err, jc.ErrorIsNil) 1651 1652 // Destroy a service with no units in relation scope; check service and 1653 // unit removed. 1654 err = wordpress.Destroy() 1655 c.Assert(err, jc.ErrorIsNil) 1656 err = wordpress.Refresh() 1657 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1658 err = rel.Refresh() 1659 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1660 } 1661 1662 func (s *ApplicationSuite) TestDestroyWithReferencedRelation(c *gc.C) { 1663 s.assertDestroyWithReferencedRelation(c, true) 1664 } 1665 1666 func (s *ApplicationSuite) TestDestroyWithReferencedRelationStaleCount(c *gc.C) { 1667 s.assertDestroyWithReferencedRelation(c, false) 1668 } 1669 1670 func (s *ApplicationSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) { 1671 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1672 eps, err := s.State.InferEndpoints("wordpress", "mysql") 1673 c.Assert(err, jc.ErrorIsNil) 1674 rel0, err := s.State.AddRelation(eps...) 1675 c.Assert(err, jc.ErrorIsNil) 1676 1677 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 1678 eps, err = s.State.InferEndpoints("logging", "mysql") 1679 c.Assert(err, jc.ErrorIsNil) 1680 rel1, err := s.State.AddRelation(eps...) 1681 c.Assert(err, jc.ErrorIsNil) 1682 1683 // Add a separate reference to the first relation. 1684 unit, err := wordpress.AddUnit() 1685 c.Assert(err, jc.ErrorIsNil) 1686 ru, err := rel0.Unit(unit) 1687 c.Assert(err, jc.ErrorIsNil) 1688 err = ru.EnterScope(nil) 1689 c.Assert(err, jc.ErrorIsNil) 1690 1691 // Optionally update the service document to get correct relation counts. 1692 if refresh { 1693 err = s.mysql.Destroy() 1694 c.Assert(err, jc.ErrorIsNil) 1695 } 1696 1697 // Destroy, and check that the first relation becomes Dying... 1698 err = s.mysql.Destroy() 1699 c.Assert(err, jc.ErrorIsNil) 1700 err = rel0.Refresh() 1701 c.Assert(err, jc.ErrorIsNil) 1702 c.Assert(rel0.Life(), gc.Equals, state.Dying) 1703 1704 // ...while the second is removed directly. 1705 err = rel1.Refresh() 1706 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1707 1708 // Drop the last reference to the first relation; check the relation and 1709 // the service are are both removed. 1710 err = ru.LeaveScope() 1711 c.Assert(err, jc.ErrorIsNil) 1712 err = s.mysql.Refresh() 1713 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1714 err = rel0.Refresh() 1715 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1716 } 1717 1718 func (s *ApplicationSuite) TestDestroyQueuesUnitCleanup(c *gc.C) { 1719 // Add 5 units; block quick-remove of mysql/1 and mysql/3 1720 units := make([]*state.Unit, 5) 1721 for i := range units { 1722 unit, err := s.mysql.AddUnit() 1723 c.Assert(err, jc.ErrorIsNil) 1724 units[i] = unit 1725 if i%2 != 0 { 1726 preventUnitDestroyRemove(c, unit) 1727 } 1728 } 1729 1730 // Check state is clean. 1731 dirty, err := s.State.NeedsCleanup() 1732 c.Assert(err, jc.ErrorIsNil) 1733 c.Assert(dirty, jc.IsFalse) 1734 1735 // Destroy mysql, and check units are not touched. 1736 err = s.mysql.Destroy() 1737 c.Assert(err, jc.ErrorIsNil) 1738 for _, unit := range units { 1739 assertLife(c, unit, state.Alive) 1740 } 1741 1742 // Check a cleanup doc was added. 1743 dirty, err = s.State.NeedsCleanup() 1744 c.Assert(err, jc.ErrorIsNil) 1745 c.Assert(dirty, jc.IsTrue) 1746 1747 // Run the cleanup and check the units. 1748 err = s.State.Cleanup() 1749 c.Assert(err, jc.ErrorIsNil) 1750 for i, unit := range units { 1751 if i%2 != 0 { 1752 assertLife(c, unit, state.Dying) 1753 } else { 1754 assertRemoved(c, unit) 1755 } 1756 } 1757 1758 // Check for queued unit cleanups, and run them. 1759 dirty, err = s.State.NeedsCleanup() 1760 c.Assert(err, jc.ErrorIsNil) 1761 c.Assert(dirty, jc.IsTrue) 1762 err = s.State.Cleanup() 1763 c.Assert(err, jc.ErrorIsNil) 1764 1765 // Check we're now clean. 1766 dirty, err = s.State.NeedsCleanup() 1767 c.Assert(err, jc.ErrorIsNil) 1768 c.Assert(dirty, jc.IsFalse) 1769 } 1770 1771 func (s *ApplicationSuite) TestRemoveServiceMachine(c *gc.C) { 1772 unit, err := s.mysql.AddUnit() 1773 c.Assert(err, jc.ErrorIsNil) 1774 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1775 c.Assert(err, jc.ErrorIsNil) 1776 c.Assert(unit.AssignToMachine(machine), gc.IsNil) 1777 1778 c.Assert(s.mysql.Destroy(), gc.IsNil) 1779 assertLife(c, s.mysql, state.Dying) 1780 1781 // Service.Destroy adds units to cleanup, make it happen now. 1782 c.Assert(s.State.Cleanup(), gc.IsNil) 1783 1784 c.Assert(unit.Refresh(), jc.Satisfies, errors.IsNotFound) 1785 assertLife(c, machine, state.Dying) 1786 } 1787 1788 func (s *ApplicationSuite) TestRemoveQueuesLocalCharmCleanup(c *gc.C) { 1789 // Check state is clean. 1790 dirty, err := s.State.NeedsCleanup() 1791 c.Assert(err, jc.ErrorIsNil) 1792 c.Assert(dirty, jc.IsFalse) 1793 1794 err = s.mysql.Destroy() 1795 1796 // Check a cleanup doc was added. 1797 dirty, err = s.State.NeedsCleanup() 1798 c.Assert(err, jc.ErrorIsNil) 1799 c.Assert(dirty, jc.IsTrue) 1800 1801 // Run the cleanup 1802 err = s.State.Cleanup() 1803 c.Assert(err, jc.ErrorIsNil) 1804 1805 // Check charm removed 1806 err = s.charm.Refresh() 1807 c.Check(err, jc.Satisfies, errors.IsNotFound) 1808 1809 // Check we're now clean. 1810 dirty, err = s.State.NeedsCleanup() 1811 c.Assert(err, jc.ErrorIsNil) 1812 c.Assert(dirty, jc.IsFalse) 1813 } 1814 1815 func (s *ApplicationSuite) TestReadUnitWithChangingState(c *gc.C) { 1816 // Check that reading a unit after removing the service 1817 // fails nicely. 1818 err := s.mysql.Destroy() 1819 c.Assert(err, jc.ErrorIsNil) 1820 err = s.mysql.Refresh() 1821 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1822 _, err = s.State.Unit("mysql/0") 1823 c.Assert(err, gc.ErrorMatches, `unit "mysql/0" not found`) 1824 } 1825 1826 func uint64p(val uint64) *uint64 { 1827 return &val 1828 } 1829 1830 func (s *ApplicationSuite) TestConstraints(c *gc.C) { 1831 // Constraints are initially empty (for now). 1832 cons, err := s.mysql.Constraints() 1833 c.Assert(err, jc.ErrorIsNil) 1834 c.Assert(&cons, jc.Satisfies, constraints.IsEmpty) 1835 1836 // Constraints can be set. 1837 cons2 := constraints.Value{Mem: uint64p(4096)} 1838 err = s.mysql.SetConstraints(cons2) 1839 cons3, err := s.mysql.Constraints() 1840 c.Assert(err, jc.ErrorIsNil) 1841 c.Assert(cons3, gc.DeepEquals, cons2) 1842 1843 // Constraints are completely overwritten when re-set. 1844 cons4 := constraints.Value{CpuPower: uint64p(750)} 1845 err = s.mysql.SetConstraints(cons4) 1846 c.Assert(err, jc.ErrorIsNil) 1847 cons5, err := s.mysql.Constraints() 1848 c.Assert(err, jc.ErrorIsNil) 1849 c.Assert(cons5, gc.DeepEquals, cons4) 1850 1851 // Destroy the existing service; there's no way to directly assert 1852 // that the constraints are deleted... 1853 err = s.mysql.Destroy() 1854 c.Assert(err, jc.ErrorIsNil) 1855 err = s.mysql.Refresh() 1856 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1857 1858 // ...but we can check that old constraints do not affect new services 1859 // with matching names. 1860 ch, _, err := s.mysql.Charm() 1861 c.Assert(err, jc.ErrorIsNil) 1862 mysql := s.AddTestingService(c, s.mysql.Name(), ch) 1863 cons6, err := mysql.Constraints() 1864 c.Assert(err, jc.ErrorIsNil) 1865 c.Assert(&cons6, jc.Satisfies, constraints.IsEmpty) 1866 } 1867 1868 func (s *ApplicationSuite) TestSetInvalidConstraints(c *gc.C) { 1869 cons := constraints.MustParse("mem=4G instance-type=foo") 1870 err := s.mysql.SetConstraints(cons) 1871 c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`) 1872 } 1873 1874 func (s *ApplicationSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) { 1875 defer loggo.ResetWriters() 1876 logger := loggo.GetLogger("test") 1877 logger.SetLogLevel(loggo.DEBUG) 1878 var tw loggo.TestWriter 1879 c.Assert(loggo.RegisterWriter("constraints-tester", &tw), gc.IsNil) 1880 1881 cons := constraints.MustParse("mem=4G cpu-power=10") 1882 err := s.mysql.SetConstraints(cons) 1883 c.Assert(err, jc.ErrorIsNil) 1884 c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{ 1885 loggo.WARNING, 1886 `setting constraints on application "mysql": unsupported constraints: cpu-power`}, 1887 }) 1888 scons, err := s.mysql.Constraints() 1889 c.Assert(err, jc.ErrorIsNil) 1890 c.Assert(scons, gc.DeepEquals, cons) 1891 } 1892 1893 func (s *ApplicationSuite) TestConstraintsLifecycle(c *gc.C) { 1894 // Dying. 1895 unit, err := s.mysql.AddUnit() 1896 c.Assert(err, jc.ErrorIsNil) 1897 err = s.mysql.Destroy() 1898 c.Assert(err, jc.ErrorIsNil) 1899 cons1 := constraints.MustParse("mem=1G") 1900 err = s.mysql.SetConstraints(cons1) 1901 c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`) 1902 scons, err := s.mysql.Constraints() 1903 c.Assert(err, jc.ErrorIsNil) 1904 c.Assert(&scons, jc.Satisfies, constraints.IsEmpty) 1905 1906 // Removed (== Dead, for a service). 1907 err = unit.EnsureDead() 1908 c.Assert(err, jc.ErrorIsNil) 1909 err = unit.Remove() 1910 c.Assert(err, jc.ErrorIsNil) 1911 err = s.mysql.SetConstraints(cons1) 1912 c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`) 1913 _, err = s.mysql.Constraints() 1914 c.Assert(err, gc.ErrorMatches, `constraints not found`) 1915 } 1916 1917 func (s *ApplicationSuite) TestSubordinateConstraints(c *gc.C) { 1918 loggingCh := s.AddTestingCharm(c, "logging") 1919 logging := s.AddTestingService(c, "logging", loggingCh) 1920 1921 _, err := logging.Constraints() 1922 c.Assert(err, gc.Equals, state.ErrSubordinateConstraints) 1923 1924 err = logging.SetConstraints(constraints.Value{}) 1925 c.Assert(err, gc.Equals, state.ErrSubordinateConstraints) 1926 } 1927 1928 func (s *ApplicationSuite) TestWatchUnitsBulkEvents(c *gc.C) { 1929 // Alive unit... 1930 alive, err := s.mysql.AddUnit() 1931 c.Assert(err, jc.ErrorIsNil) 1932 1933 // Dying unit... 1934 dying, err := s.mysql.AddUnit() 1935 c.Assert(err, jc.ErrorIsNil) 1936 preventUnitDestroyRemove(c, dying) 1937 err = dying.Destroy() 1938 c.Assert(err, jc.ErrorIsNil) 1939 1940 // Dead unit... 1941 dead, err := s.mysql.AddUnit() 1942 c.Assert(err, jc.ErrorIsNil) 1943 preventUnitDestroyRemove(c, dead) 1944 err = dead.Destroy() 1945 c.Assert(err, jc.ErrorIsNil) 1946 err = dead.EnsureDead() 1947 c.Assert(err, jc.ErrorIsNil) 1948 1949 // Gone unit. 1950 gone, err := s.mysql.AddUnit() 1951 c.Assert(err, jc.ErrorIsNil) 1952 err = gone.Destroy() 1953 c.Assert(err, jc.ErrorIsNil) 1954 1955 // All except gone unit are reported in initial event. 1956 w := s.mysql.WatchUnits() 1957 defer testing.AssertStop(c, w) 1958 wc := testing.NewStringsWatcherC(c, s.State, w) 1959 wc.AssertChange(alive.Name(), dying.Name(), dead.Name()) 1960 wc.AssertNoChange() 1961 1962 // Remove them all; alive/dying changes reported; dead never mentioned again. 1963 err = alive.Destroy() 1964 c.Assert(err, jc.ErrorIsNil) 1965 err = dying.EnsureDead() 1966 c.Assert(err, jc.ErrorIsNil) 1967 err = dying.Remove() 1968 c.Assert(err, jc.ErrorIsNil) 1969 err = dead.Remove() 1970 c.Assert(err, jc.ErrorIsNil) 1971 wc.AssertChange(alive.Name(), dying.Name()) 1972 wc.AssertNoChange() 1973 } 1974 1975 func (s *ApplicationSuite) TestWatchUnitsLifecycle(c *gc.C) { 1976 // Empty initial event when no units. 1977 w := s.mysql.WatchUnits() 1978 defer testing.AssertStop(c, w) 1979 wc := testing.NewStringsWatcherC(c, s.State, w) 1980 wc.AssertChange() 1981 wc.AssertNoChange() 1982 1983 // Create one unit, check one change. 1984 quick, err := s.mysql.AddUnit() 1985 c.Assert(err, jc.ErrorIsNil) 1986 wc.AssertChange(quick.Name()) 1987 wc.AssertNoChange() 1988 1989 // Destroy that unit (short-circuited to removal), check one change. 1990 err = quick.Destroy() 1991 c.Assert(err, jc.ErrorIsNil) 1992 wc.AssertChange(quick.Name()) 1993 wc.AssertNoChange() 1994 1995 // Create another, check one change. 1996 slow, err := s.mysql.AddUnit() 1997 c.Assert(err, jc.ErrorIsNil) 1998 wc.AssertChange(slow.Name()) 1999 wc.AssertNoChange() 2000 2001 // Change unit itself, no change. 2002 preventUnitDestroyRemove(c, slow) 2003 wc.AssertNoChange() 2004 2005 // Make unit Dying, change detected. 2006 err = slow.Destroy() 2007 c.Assert(err, jc.ErrorIsNil) 2008 wc.AssertChange(slow.Name()) 2009 wc.AssertNoChange() 2010 2011 // Make unit Dead, change detected. 2012 err = slow.EnsureDead() 2013 c.Assert(err, jc.ErrorIsNil) 2014 wc.AssertChange(slow.Name()) 2015 wc.AssertNoChange() 2016 2017 // Remove unit, final change not detected. 2018 err = slow.Remove() 2019 c.Assert(err, jc.ErrorIsNil) 2020 wc.AssertNoChange() 2021 } 2022 2023 func (s *ApplicationSuite) TestWatchRelations(c *gc.C) { 2024 // TODO(fwereade) split this test up a bit. 2025 w := s.mysql.WatchRelations() 2026 defer testing.AssertStop(c, w) 2027 wc := testing.NewStringsWatcherC(c, s.State, w) 2028 wc.AssertChange() 2029 wc.AssertNoChange() 2030 2031 // Add a relation; check change. 2032 mysqlep, err := s.mysql.Endpoint("server") 2033 c.Assert(err, jc.ErrorIsNil) 2034 wpch := s.AddTestingCharm(c, "wordpress") 2035 wpi := 0 2036 addRelation := func() *state.Relation { 2037 name := fmt.Sprintf("wp%d", wpi) 2038 wpi++ 2039 wp := s.AddTestingService(c, name, wpch) 2040 wpep, err := wp.Endpoint("db") 2041 c.Assert(err, jc.ErrorIsNil) 2042 rel, err := s.State.AddRelation(mysqlep, wpep) 2043 c.Assert(err, jc.ErrorIsNil) 2044 return rel 2045 } 2046 rel0 := addRelation() 2047 wc.AssertChange(rel0.String()) 2048 wc.AssertNoChange() 2049 2050 // Add another relation; check change. 2051 rel1 := addRelation() 2052 wc.AssertChange(rel1.String()) 2053 wc.AssertNoChange() 2054 2055 // Destroy a relation; check change. 2056 err = rel0.Destroy() 2057 c.Assert(err, jc.ErrorIsNil) 2058 wc.AssertChange(rel0.String()) 2059 wc.AssertNoChange() 2060 2061 // Stop watcher; check change chan is closed. 2062 testing.AssertStop(c, w) 2063 wc.AssertClosed() 2064 2065 // Add a new relation; start a new watcher; check initial event. 2066 rel2 := addRelation() 2067 w = s.mysql.WatchRelations() 2068 defer testing.AssertStop(c, w) 2069 wc = testing.NewStringsWatcherC(c, s.State, w) 2070 wc.AssertChange(rel1.String(), rel2.String()) 2071 wc.AssertNoChange() 2072 2073 // Add a unit to the new relation; check no change. 2074 unit, err := s.mysql.AddUnit() 2075 c.Assert(err, jc.ErrorIsNil) 2076 ru2, err := rel2.Unit(unit) 2077 c.Assert(err, jc.ErrorIsNil) 2078 err = ru2.EnterScope(nil) 2079 c.Assert(err, jc.ErrorIsNil) 2080 wc.AssertNoChange() 2081 2082 // Destroy the relation with the unit in scope, and add another; check 2083 // changes. 2084 err = rel2.Destroy() 2085 c.Assert(err, jc.ErrorIsNil) 2086 rel3 := addRelation() 2087 wc.AssertChange(rel2.String(), rel3.String()) 2088 wc.AssertNoChange() 2089 2090 // Leave scope, destroying the relation, and check that change as well. 2091 err = ru2.LeaveScope() 2092 c.Assert(err, jc.ErrorIsNil) 2093 wc.AssertChange(rel2.String()) 2094 wc.AssertNoChange() 2095 2096 // Watch relations on the requirer service too (exercises a 2097 // different path of the WatchRelations filter function) 2098 wpx := s.AddTestingService(c, "wpx", wpch) 2099 wpxWatcher := wpx.WatchRelations() 2100 defer testing.AssertStop(c, wpxWatcher) 2101 wpxWatcherC := testing.NewStringsWatcherC(c, s.State, wpxWatcher) 2102 wpxWatcherC.AssertChange() 2103 wpxWatcherC.AssertNoChange() 2104 2105 wpxep, err := wpx.Endpoint("db") 2106 c.Assert(err, jc.ErrorIsNil) 2107 relx, err := s.State.AddRelation(mysqlep, wpxep) 2108 c.Assert(err, jc.ErrorIsNil) 2109 wpxWatcherC.AssertChange(relx.String()) 2110 wpxWatcherC.AssertNoChange() 2111 } 2112 2113 func removeAllUnits(c *gc.C, s *state.Application) { 2114 us, err := s.AllUnits() 2115 c.Assert(err, jc.ErrorIsNil) 2116 for _, u := range us { 2117 err = u.EnsureDead() 2118 c.Assert(err, jc.ErrorIsNil) 2119 err = u.Remove() 2120 c.Assert(err, jc.ErrorIsNil) 2121 } 2122 } 2123 2124 func (s *ApplicationSuite) TestWatchService(c *gc.C) { 2125 w := s.mysql.Watch() 2126 defer testing.AssertStop(c, w) 2127 2128 // Initial event. 2129 wc := testing.NewNotifyWatcherC(c, s.State, w) 2130 wc.AssertOneChange() 2131 2132 // Make one change (to a separate instance), check one event. 2133 service, err := s.State.Application(s.mysql.Name()) 2134 c.Assert(err, jc.ErrorIsNil) 2135 err = service.SetExposed() 2136 c.Assert(err, jc.ErrorIsNil) 2137 wc.AssertOneChange() 2138 2139 // Make two changes, check one event. 2140 err = service.ClearExposed() 2141 c.Assert(err, jc.ErrorIsNil) 2142 2143 cfg := state.SetCharmConfig{ 2144 Charm: s.charm, 2145 ForceUnits: true, 2146 } 2147 err = service.SetCharm(cfg) 2148 c.Assert(err, jc.ErrorIsNil) 2149 wc.AssertOneChange() 2150 2151 // Stop, check closed. 2152 testing.AssertStop(c, w) 2153 wc.AssertClosed() 2154 2155 // Remove service, start new watch, check single event. 2156 err = service.Destroy() 2157 c.Assert(err, jc.ErrorIsNil) 2158 w = s.mysql.Watch() 2159 defer testing.AssertStop(c, w) 2160 testing.NewNotifyWatcherC(c, s.State, w).AssertOneChange() 2161 } 2162 2163 func (s *ApplicationSuite) TestMetricCredentials(c *gc.C) { 2164 err := s.mysql.SetMetricCredentials([]byte("hello there")) 2165 c.Assert(err, jc.ErrorIsNil) 2166 c.Assert(s.mysql.MetricCredentials(), gc.DeepEquals, []byte("hello there")) 2167 2168 service, err := s.State.Application(s.mysql.Name()) 2169 c.Assert(service.MetricCredentials(), gc.DeepEquals, []byte("hello there")) 2170 } 2171 2172 func (s *ApplicationSuite) TestMetricCredentialsOnDying(c *gc.C) { 2173 _, err := s.mysql.AddUnit() 2174 c.Assert(err, jc.ErrorIsNil) 2175 err = s.mysql.SetMetricCredentials([]byte("set before dying")) 2176 c.Assert(err, jc.ErrorIsNil) 2177 err = s.mysql.Destroy() 2178 c.Assert(err, jc.ErrorIsNil) 2179 assertLife(c, s.mysql, state.Dying) 2180 err = s.mysql.SetMetricCredentials([]byte("set after dying")) 2181 c.Assert(err, gc.ErrorMatches, "cannot update metric credentials: application not found or not alive") 2182 } 2183 2184 func (s *ApplicationSuite) testStatus(c *gc.C, status1, status2, expected status.Status) { 2185 u1, err := s.mysql.AddUnit() 2186 c.Assert(err, jc.ErrorIsNil) 2187 now := coretesting.ZeroTime() 2188 sInfo := status.StatusInfo{ 2189 Status: status1, 2190 Message: "status 1", 2191 Since: &now, 2192 } 2193 err = u1.SetStatus(sInfo) 2194 c.Assert(err, jc.ErrorIsNil) 2195 2196 u2, err := s.mysql.AddUnit() 2197 c.Assert(err, jc.ErrorIsNil) 2198 sInfo = status.StatusInfo{ 2199 Status: status2, 2200 Message: "status 2", 2201 Since: &now, 2202 } 2203 if status2 == status.Error { 2204 err = u2.SetAgentStatus(sInfo) 2205 } else { 2206 err = u2.SetStatus(sInfo) 2207 } 2208 c.Assert(err, jc.ErrorIsNil) 2209 2210 statusInfo, err := s.mysql.Status() 2211 c.Assert(err, jc.ErrorIsNil) 2212 c.Assert(statusInfo.Since, gc.NotNil) 2213 statusInfo.Since = nil 2214 c.Assert(statusInfo, jc.DeepEquals, status.StatusInfo{ 2215 Status: expected, 2216 Message: "status 2", 2217 Data: map[string]interface{}{}, 2218 }) 2219 } 2220 2221 func (s *ApplicationSuite) TestStatus(c *gc.C) { 2222 for _, t := range []struct{ status1, status2, expected status.Status }{ 2223 {status.Active, status.Waiting, status.Waiting}, 2224 {status.Maintenance, status.Waiting, status.Waiting}, 2225 {status.Active, status.Blocked, status.Blocked}, 2226 {status.Waiting, status.Blocked, status.Blocked}, 2227 {status.Maintenance, status.Blocked, status.Blocked}, 2228 {status.Maintenance, status.Error, status.Error}, 2229 {status.Blocked, status.Error, status.Error}, 2230 {status.Waiting, status.Error, status.Error}, 2231 {status.Active, status.Error, status.Error}, 2232 } { 2233 s.testStatus(c, t.status1, t.status2, t.expected) 2234 } 2235 } 2236 2237 const oneRequiredStorageMeta = ` 2238 storage: 2239 data0: 2240 type: block 2241 ` 2242 2243 const oneOptionalStorageMeta = ` 2244 storage: 2245 data0: 2246 type: block 2247 multiple: 2248 range: 0- 2249 ` 2250 2251 const oneRequiredOneOptionalStorageMeta = ` 2252 storage: 2253 data0: 2254 type: block 2255 data1: 2256 type: block 2257 multiple: 2258 range: 0- 2259 ` 2260 2261 const twoRequiredStorageMeta = ` 2262 storage: 2263 data0: 2264 type: block 2265 data1: 2266 type: block 2267 ` 2268 2269 const twoOptionalStorageMeta = ` 2270 storage: 2271 data0: 2272 type: block 2273 multiple: 2274 range: 0- 2275 data1: 2276 type: block 2277 multiple: 2278 range: 0- 2279 ` 2280 2281 const oneRequiredFilesystemStorageMeta = ` 2282 storage: 2283 data0: 2284 type: filesystem 2285 ` 2286 2287 const oneOptionalSharedStorageMeta = ` 2288 storage: 2289 data0: 2290 type: block 2291 shared: true 2292 multiple: 2293 range: 0- 2294 ` 2295 2296 const oneRequiredReadOnlyStorageMeta = ` 2297 storage: 2298 data0: 2299 type: block 2300 read-only: true 2301 ` 2302 2303 const oneRequiredLocationStorageMeta = ` 2304 storage: 2305 data0: 2306 type: filesystem 2307 location: /srv 2308 ` 2309 2310 const oneMultipleLocationStorageMeta = ` 2311 storage: 2312 data0: 2313 type: filesystem 2314 location: /srv 2315 multiple: 2316 range: 1- 2317 ` 2318 2319 func storageRange(min, max int) string { 2320 var minStr, maxStr string 2321 if min > 0 { 2322 minStr = fmt.Sprint(min) 2323 } 2324 if max > 0 { 2325 maxStr = fmt.Sprint(max) 2326 } 2327 return fmt.Sprintf(` 2328 multiple: 2329 range: %s-%s 2330 `[1:], minStr, maxStr) 2331 } 2332 2333 func (s *ApplicationSuite) setCharmFromMeta(c *gc.C, oldMeta, newMeta string) error { 2334 oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2) 2335 newCh := s.AddMetaCharm(c, "mysql", newMeta, 3) 2336 svc := s.AddTestingService(c, "test", oldCh) 2337 2338 cfg := state.SetCharmConfig{Charm: newCh} 2339 return svc.SetCharm(cfg) 2340 } 2341 2342 func (s *ApplicationSuite) TestSetCharmOptionalUnusedStorageRemoved(c *gc.C) { 2343 err := s.setCharmFromMeta(c, 2344 mysqlBaseMeta+oneRequiredOneOptionalStorageMeta, 2345 mysqlBaseMeta+oneRequiredStorageMeta, 2346 ) 2347 c.Assert(err, jc.ErrorIsNil) 2348 // It's valid to remove optional storage so long 2349 // as it is not in use. 2350 } 2351 2352 func (s *ApplicationSuite) TestSetCharmOptionalUsedStorageRemoved(c *gc.C) { 2353 oldMeta := mysqlBaseMeta + oneRequiredOneOptionalStorageMeta 2354 newMeta := mysqlBaseMeta + oneRequiredStorageMeta 2355 oldCh := s.AddMetaCharm(c, "mysql", oldMeta, 2) 2356 newCh := s.AddMetaCharm(c, "mysql", newMeta, 3) 2357 svc := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 2358 Name: "test", 2359 Charm: oldCh, 2360 Storage: map[string]state.StorageConstraints{ 2361 "data0": {Count: 1}, 2362 "data1": {Count: 1}, 2363 }, 2364 }) 2365 defer state.SetBeforeHooks(c, s.State, func() { 2366 // Adding a unit will cause the storage to be in-use. 2367 _, err := svc.AddUnit() 2368 c.Assert(err, jc.ErrorIsNil) 2369 }).Check() 2370 cfg := state.SetCharmConfig{Charm: newCh} 2371 err := svc.SetCharm(cfg) 2372 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": in-use storage "data1" removed`) 2373 } 2374 2375 func (s *ApplicationSuite) TestSetCharmRequiredStorageRemoved(c *gc.C) { 2376 err := s.setCharmFromMeta(c, 2377 mysqlBaseMeta+oneRequiredStorageMeta, 2378 mysqlBaseMeta, 2379 ) 2380 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": required storage "data0" removed`) 2381 } 2382 2383 func (s *ApplicationSuite) TestSetCharmRequiredStorageAddedDefaultConstraints(c *gc.C) { 2384 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2) 2385 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoRequiredStorageMeta, 3) 2386 svc := s.AddTestingService(c, "test", oldCh) 2387 u, err := svc.AddUnit() 2388 c.Assert(err, jc.ErrorIsNil) 2389 2390 cfg := state.SetCharmConfig{Charm: newCh} 2391 err = svc.SetCharm(cfg) 2392 c.Assert(err, jc.ErrorIsNil) 2393 2394 // Check that the new required storage was added for the unit. 2395 attachments, err := s.State.UnitStorageAttachments(u.UnitTag()) 2396 c.Assert(err, jc.ErrorIsNil) 2397 c.Assert(attachments, gc.HasLen, 2) 2398 } 2399 2400 func (s *ApplicationSuite) TestSetCharmStorageAddedUserSpecifiedConstraints(c *gc.C) { 2401 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+oneRequiredStorageMeta, 2) 2402 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoOptionalStorageMeta, 3) 2403 svc := s.AddTestingService(c, "test", oldCh) 2404 u, err := svc.AddUnit() 2405 c.Assert(err, jc.ErrorIsNil) 2406 2407 cfg := state.SetCharmConfig{ 2408 Charm: newCh, 2409 StorageConstraints: map[string]state.StorageConstraints{ 2410 "data1": {Count: 3}, 2411 }, 2412 } 2413 err = svc.SetCharm(cfg) 2414 c.Assert(err, jc.ErrorIsNil) 2415 2416 // Check that new storage was added for the unit, based on the 2417 // constraints specified in SetCharmConfig. 2418 attachments, err := s.State.UnitStorageAttachments(u.UnitTag()) 2419 c.Assert(err, jc.ErrorIsNil) 2420 c.Assert(attachments, gc.HasLen, 4) 2421 } 2422 2423 func (s *ApplicationSuite) TestSetCharmOptionalStorageAdded(c *gc.C) { 2424 err := s.setCharmFromMeta(c, 2425 mysqlBaseMeta+oneRequiredStorageMeta, 2426 mysqlBaseMeta+twoOptionalStorageMeta, 2427 ) 2428 c.Assert(err, jc.ErrorIsNil) 2429 } 2430 2431 func (s *ApplicationSuite) TestSetCharmStorageCountMinDecreased(c *gc.C) { 2432 err := s.setCharmFromMeta(c, 2433 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3), 2434 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3), 2435 ) 2436 c.Assert(err, jc.ErrorIsNil) 2437 } 2438 2439 func (s *ApplicationSuite) TestSetCharmStorageCountMinIncreased(c *gc.C) { 2440 err := s.setCharmFromMeta(c, 2441 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 3), 2442 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(2, 3), 2443 ) 2444 // User must increase the storage constraints from 1 to 2. 2445 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": validating storage constraints: charm "mysql" store "data0": 2 instances required, 1 specified`) 2446 } 2447 2448 func (s *ApplicationSuite) TestSetCharmStorageCountMaxDecreased(c *gc.C) { 2449 err := s.setCharmFromMeta(c, 2450 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 2), 2451 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 1), 2452 ) 2453 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" range contracted: max decreased from 2 to 1`) 2454 } 2455 2456 func (s *ApplicationSuite) TestSetCharmStorageCountMaxUnboundedToBounded(c *gc.C) { 2457 err := s.setCharmFromMeta(c, 2458 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, -1), 2459 mysqlBaseMeta+oneRequiredStorageMeta+storageRange(1, 999), 2460 ) 2461 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" range contracted: max decreased from \<unbounded\> to 999`) 2462 } 2463 2464 func (s *ApplicationSuite) TestSetCharmStorageTypeChanged(c *gc.C) { 2465 err := s.setCharmFromMeta(c, 2466 mysqlBaseMeta+oneRequiredStorageMeta, 2467 mysqlBaseMeta+oneRequiredFilesystemStorageMeta, 2468 ) 2469 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" type changed from "block" to "filesystem"`) 2470 } 2471 2472 func (s *ApplicationSuite) TestSetCharmStorageSharedChanged(c *gc.C) { 2473 err := s.setCharmFromMeta(c, 2474 mysqlBaseMeta+oneOptionalStorageMeta, 2475 mysqlBaseMeta+oneOptionalSharedStorageMeta, 2476 ) 2477 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" shared changed from false to true`) 2478 } 2479 2480 func (s *ApplicationSuite) TestSetCharmStorageReadOnlyChanged(c *gc.C) { 2481 err := s.setCharmFromMeta(c, 2482 mysqlBaseMeta+oneRequiredStorageMeta, 2483 mysqlBaseMeta+oneRequiredReadOnlyStorageMeta, 2484 ) 2485 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" read-only changed from false to true`) 2486 } 2487 2488 func (s *ApplicationSuite) TestSetCharmStorageLocationChanged(c *gc.C) { 2489 err := s.setCharmFromMeta(c, 2490 mysqlBaseMeta+oneRequiredFilesystemStorageMeta, 2491 mysqlBaseMeta+oneRequiredLocationStorageMeta, 2492 ) 2493 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" location changed from "" to "/srv"`) 2494 } 2495 2496 func (s *ApplicationSuite) TestSetCharmStorageWithLocationSingletonToMultipleAdded(c *gc.C) { 2497 err := s.setCharmFromMeta(c, 2498 mysqlBaseMeta+oneRequiredLocationStorageMeta, 2499 mysqlBaseMeta+oneMultipleLocationStorageMeta, 2500 ) 2501 c.Assert(err, gc.ErrorMatches, `cannot upgrade application "test" to charm "local:quantal/quantal-mysql-3": existing storage "data0" with location changed from single to multiple`) 2502 } 2503 2504 func (s *ApplicationSuite) assertApplicationRemovedWithItsBindings(c *gc.C, service *state.Application) { 2505 // Removing the service removes the bindings with it. 2506 err := service.Destroy() 2507 c.Assert(err, jc.ErrorIsNil) 2508 err = service.Refresh() 2509 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2510 state.AssertEndpointBindingsNotFoundForService(c, service) 2511 } 2512 2513 func (s *ApplicationSuite) TestEndpointBindingsReturnsDefaultsWhenNotFound(c *gc.C) { 2514 ch := s.AddMetaCharm(c, "mysql", metaBase, 42) 2515 service := s.AddTestingServiceWithBindings(c, "yoursql", ch, nil) 2516 state.RemoveEndpointBindingsForService(c, service) 2517 2518 s.assertApplicationHasOnlyDefaultEndpointBindings(c, service) 2519 } 2520 2521 func (s *ApplicationSuite) assertApplicationHasOnlyDefaultEndpointBindings(c *gc.C, service *state.Application) { 2522 charm, _, err := service.Charm() 2523 c.Assert(err, jc.ErrorIsNil) 2524 2525 knownEndpoints := set.NewStrings() 2526 allBindings := state.DefaultEndpointBindingsForCharm(charm.Meta()) 2527 for endpoint, _ := range allBindings { 2528 knownEndpoints.Add(endpoint) 2529 } 2530 2531 setBindings, err := service.EndpointBindings() 2532 c.Assert(err, jc.ErrorIsNil) 2533 c.Assert(setBindings, gc.NotNil) 2534 2535 for endpoint, space := range setBindings { 2536 c.Check(endpoint, gc.Not(gc.Equals), "") 2537 c.Check(knownEndpoints.Contains(endpoint), jc.IsTrue) 2538 c.Check(space, gc.Equals, "", gc.Commentf("expected empty space for endpoint %q, got %q", endpoint, space)) 2539 } 2540 } 2541 2542 func (s *ApplicationSuite) TestEndpointBindingsJustDefaults(c *gc.C) { 2543 // With unspecified bindings, all endpoints are explicitly bound to the 2544 // default space when saved in state. 2545 ch := s.AddMetaCharm(c, "mysql", metaBase, 42) 2546 service := s.AddTestingServiceWithBindings(c, "yoursql", ch, nil) 2547 2548 s.assertApplicationHasOnlyDefaultEndpointBindings(c, service) 2549 s.assertApplicationRemovedWithItsBindings(c, service) 2550 } 2551 2552 func (s *ApplicationSuite) TestEndpointBindingsWithExplictOverrides(c *gc.C) { 2553 _, err := s.State.AddSpace("db", "", nil, true) 2554 c.Assert(err, jc.ErrorIsNil) 2555 _, err = s.State.AddSpace("ha", "", nil, false) 2556 c.Assert(err, jc.ErrorIsNil) 2557 2558 bindings := map[string]string{ 2559 "server": "db", 2560 "cluster": "ha", 2561 } 2562 ch := s.AddMetaCharm(c, "mysql", metaBase, 42) 2563 service := s.AddTestingServiceWithBindings(c, "yoursql", ch, bindings) 2564 2565 setBindings, err := service.EndpointBindings() 2566 c.Assert(err, jc.ErrorIsNil) 2567 c.Assert(setBindings, jc.DeepEquals, map[string]string{ 2568 "server": "db", 2569 "client": "", 2570 "cluster": "ha", 2571 }) 2572 2573 s.assertApplicationRemovedWithItsBindings(c, service) 2574 } 2575 2576 func (s *ApplicationSuite) TestSetCharmExtraBindingsUseDefaults(c *gc.C) { 2577 _, err := s.State.AddSpace("db", "", nil, true) 2578 c.Assert(err, jc.ErrorIsNil) 2579 2580 oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 42) 2581 oldBindings := map[string]string{ 2582 "kludge": "db", 2583 "client": "db", 2584 } 2585 service := s.AddTestingServiceWithBindings(c, "yoursql", oldCharm, oldBindings) 2586 setBindings, err := service.EndpointBindings() 2587 c.Assert(err, jc.ErrorIsNil) 2588 effectiveOld := map[string]string{ 2589 "kludge": "db", 2590 "client": "db", 2591 "cluster": "", 2592 } 2593 c.Assert(setBindings, jc.DeepEquals, effectiveOld) 2594 2595 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 43) 2596 2597 cfg := state.SetCharmConfig{Charm: newCharm} 2598 err = service.SetCharm(cfg) 2599 c.Assert(err, jc.ErrorIsNil) 2600 setBindings, err = service.EndpointBindings() 2601 c.Assert(err, jc.ErrorIsNil) 2602 effectiveNew := map[string]string{ 2603 // These two should be preserved from oldCharm. 2604 "client": "db", 2605 "cluster": "", 2606 // "kludge" is missing in newMeta, "server" is new and gets the default. 2607 "server": "", 2608 // All the remaining are new and use the empty default. 2609 "foo": "", 2610 "baz": "", 2611 "just": "", 2612 } 2613 c.Assert(setBindings, jc.DeepEquals, effectiveNew) 2614 2615 s.assertApplicationRemovedWithItsBindings(c, service) 2616 } 2617 2618 func (s *ApplicationSuite) TestSetCharmHandlesMissingBindingsAsDefaults(c *gc.C) { 2619 oldCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, 69) 2620 service := s.AddTestingServiceWithBindings(c, "theirsql", oldCharm, nil) 2621 state.RemoveEndpointBindingsForService(c, service) 2622 2623 newCharm := s.AddMetaCharm(c, "mysql", metaExtraEndpoints, 70) 2624 2625 cfg := state.SetCharmConfig{Charm: newCharm} 2626 err := service.SetCharm(cfg) 2627 c.Assert(err, jc.ErrorIsNil) 2628 setBindings, err := service.EndpointBindings() 2629 c.Assert(err, jc.ErrorIsNil) 2630 effectiveNew := map[string]string{ 2631 // The following two exist for both oldCharm and newCharm. 2632 "client": "", 2633 "cluster": "", 2634 // "kludge" is missing in newMeta, "server" is new and gets the default. 2635 "server": "", 2636 // All the remaining are new and use the empty default. 2637 "foo": "", 2638 "baz": "", 2639 "just": "", 2640 } 2641 c.Assert(setBindings, jc.DeepEquals, effectiveNew) 2642 2643 s.assertApplicationRemovedWithItsBindings(c, service) 2644 }