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