launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "labix.org/v2/mgo" 11 errgo "launchpad.net/errgo/errors" 12 gc "launchpad.net/gocheck" 13 14 "launchpad.net/juju-core/charm" 15 "launchpad.net/juju-core/constraints" 16 "launchpad.net/juju-core/errors" 17 "launchpad.net/juju-core/state" 18 "launchpad.net/juju-core/state/testing" 19 jc "launchpad.net/juju-core/testing/checkers" 20 ) 21 22 type ServiceSuite struct { 23 ConnSuite 24 charm *state.Charm 25 mysql *state.Service 26 } 27 28 var _ = gc.Suite(&ServiceSuite{}) 29 30 func (s *ServiceSuite) SetUpTest(c *gc.C) { 31 s.ConnSuite.SetUpTest(c) 32 s.charm = s.AddTestingCharm(c, "mysql") 33 s.mysql = s.AddTestingService(c, "mysql", s.charm) 34 } 35 36 func (s *ServiceSuite) TestSetCharm(c *gc.C) { 37 ch, force, err := s.mysql.Charm() 38 c.Assert(err, gc.IsNil) 39 c.Assert(ch.URL(), gc.DeepEquals, s.charm.URL()) 40 c.Assert(force, gc.Equals, false) 41 url, force := s.mysql.CharmURL() 42 c.Assert(url, gc.DeepEquals, s.charm.URL()) 43 c.Assert(force, gc.Equals, false) 44 45 // Add a compatible charm and force it. 46 sch := s.AddMetaCharm(c, "mysql", metaBase, 2) // revno 1 is used by SetUpSuite 47 err = s.mysql.SetCharm(sch, true) 48 c.Assert(err, gc.IsNil) 49 ch, force, err = s.mysql.Charm() 50 c.Assert(err, gc.IsNil) 51 c.Assert(ch.URL(), gc.DeepEquals, sch.URL()) 52 c.Assert(force, gc.Equals, true) 53 url, force = s.mysql.CharmURL() 54 c.Assert(url, gc.DeepEquals, sch.URL()) 55 c.Assert(force, gc.Equals, true) 56 57 // SetCharm fails when the service is Dying. 58 _, err = s.mysql.AddUnit() 59 c.Assert(err, gc.IsNil) 60 err = s.mysql.Destroy() 61 c.Assert(err, gc.IsNil) 62 err = s.mysql.SetCharm(sch, true) 63 c.Assert(err, gc.ErrorMatches, `service "mysql" is not alive`) 64 } 65 66 func (s *ServiceSuite) TestSetCharmErrors(c *gc.C) { 67 logging := s.AddTestingCharm(c, "logging") 68 err := s.mysql.SetCharm(logging, false) 69 c.Assert(err, gc.ErrorMatches, "cannot change a service's subordinacy") 70 71 othermysql := s.AddSeriesCharm(c, "mysql", "otherseries") 72 err = s.mysql.SetCharm(othermysql, false) 73 c.Assert(err, gc.ErrorMatches, "cannot change a service's series") 74 } 75 76 var metaBase = ` 77 name: mysql 78 summary: "Fake MySQL Database engine" 79 description: "Complete with nonsense relations" 80 provides: 81 server: mysql 82 requires: 83 client: mysql 84 peers: 85 cluster: mysql 86 ` 87 var metaDifferentProvider = ` 88 name: mysql 89 description: none 90 summary: none 91 provides: 92 kludge: mysql 93 requires: 94 client: mysql 95 peers: 96 cluster: mysql 97 ` 98 var metaDifferentRequirer = ` 99 name: mysql 100 description: none 101 summary: none 102 provides: 103 server: mysql 104 requires: 105 kludge: mysql 106 peers: 107 cluster: mysql 108 ` 109 var metaDifferentPeer = ` 110 name: mysql 111 description: none 112 summary: none 113 provides: 114 server: mysql 115 requires: 116 client: mysql 117 peers: 118 kludge: mysql 119 ` 120 var metaExtraEndpoints = ` 121 name: mysql 122 description: none 123 summary: none 124 provides: 125 server: mysql 126 foo: bar 127 requires: 128 client: mysql 129 baz: woot 130 peers: 131 cluster: mysql 132 just: me 133 ` 134 135 var setCharmEndpointsTests = []struct { 136 summary string 137 meta string 138 err string 139 }{ 140 { 141 summary: "different provider (but no relation yet)", 142 meta: metaDifferentProvider, 143 }, { 144 summary: "different requirer (but no relation yet)", 145 meta: metaDifferentRequirer, 146 }, { 147 summary: "different peer", 148 meta: metaDifferentPeer, 149 err: `cannot upgrade service "fakemysql" to charm "local:quantal/quantal-mysql-5": would break relation "fakemysql:cluster"`, 150 }, { 151 summary: "same relations ok", 152 meta: metaBase, 153 }, { 154 summary: "extra endpoints ok", 155 meta: metaExtraEndpoints, 156 }, 157 } 158 159 func (s *ServiceSuite) TestSetCharmChecksEndpointsWithoutRelations(c *gc.C) { 160 revno := 2 // 1 is used in SetUpSuite 161 ms := s.AddMetaCharm(c, "mysql", metaBase, revno) 162 svc := s.AddTestingService(c, "fakemysql", ms) 163 err := svc.SetCharm(ms, false) 164 c.Assert(err, gc.IsNil) 165 166 for i, t := range setCharmEndpointsTests { 167 c.Logf("test %d: %s", i, t.summary) 168 169 newCh := s.AddMetaCharm(c, "mysql", t.meta, revno+i+1) 170 err = svc.SetCharm(newCh, false) 171 if t.err != "" { 172 c.Assert(err, gc.ErrorMatches, t.err) 173 } else { 174 c.Assert(err, gc.IsNil) 175 } 176 } 177 178 err = svc.Destroy() 179 c.Assert(err, gc.IsNil) 180 } 181 182 func (s *ServiceSuite) TestSetCharmChecksEndpointsWithRelations(c *gc.C) { 183 revno := 2 // 1 is used by SetUpSuite 184 providerCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, revno) 185 providerSvc := s.AddTestingService(c, "myprovider", providerCharm) 186 err := providerSvc.SetCharm(providerCharm, false) 187 c.Assert(err, gc.IsNil) 188 189 revno++ 190 requirerCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno) 191 requirerSvc := s.AddTestingService(c, "myrequirer", requirerCharm) 192 err = requirerSvc.SetCharm(requirerCharm, false) 193 c.Assert(err, gc.IsNil) 194 195 eps, err := s.State.InferEndpoints([]string{"myprovider:kludge", "myrequirer:kludge"}) 196 c.Assert(err, gc.IsNil) 197 _, err = s.State.AddRelation(eps...) 198 c.Assert(err, gc.IsNil) 199 200 revno++ 201 baseCharm := s.AddMetaCharm(c, "mysql", metaBase, revno) 202 err = providerSvc.SetCharm(baseCharm, false) 203 c.Assert(err, gc.ErrorMatches, `cannot upgrade service "myprovider" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`) 204 err = requirerSvc.SetCharm(baseCharm, false) 205 c.Assert(err, gc.ErrorMatches, `cannot upgrade service "myrequirer" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`) 206 } 207 208 var stringConfig = ` 209 options: 210 key: {default: My Key, description: Desc, type: string} 211 ` 212 var emptyConfig = ` 213 options: {} 214 ` 215 var floatConfig = ` 216 options: 217 key: {default: 0.42, description: Float key, type: float} 218 ` 219 var newStringConfig = ` 220 options: 221 key: {default: My Key, description: Desc, type: string} 222 other: {default: None, description: My Other, type: string} 223 ` 224 225 var setCharmConfigTests = []struct { 226 summary string 227 startconfig string 228 startvalues charm.Settings 229 endconfig string 230 endvalues charm.Settings 231 err string 232 }{ 233 { 234 summary: "add float key to empty config", 235 startconfig: emptyConfig, 236 endconfig: floatConfig, 237 }, { 238 summary: "add string key to empty config", 239 startconfig: emptyConfig, 240 endconfig: stringConfig, 241 }, { 242 summary: "add string key and preserve existing values", 243 startconfig: stringConfig, 244 startvalues: charm.Settings{"key": "foo"}, 245 endconfig: newStringConfig, 246 endvalues: charm.Settings{"key": "foo"}, 247 }, { 248 summary: "remove string key", 249 startconfig: stringConfig, 250 startvalues: charm.Settings{"key": "value"}, 251 endconfig: emptyConfig, 252 }, { 253 summary: "remove float key", 254 startconfig: floatConfig, 255 startvalues: charm.Settings{"key": 123.45}, 256 endconfig: emptyConfig, 257 }, { 258 summary: "change key type without values", 259 startconfig: stringConfig, 260 endconfig: floatConfig, 261 }, { 262 summary: "change key type with values", 263 startconfig: stringConfig, 264 startvalues: charm.Settings{"key": "value"}, 265 endconfig: floatConfig, 266 }, 267 } 268 269 func (s *ServiceSuite) TestSetCharmConfig(c *gc.C) { 270 charms := map[string]*state.Charm{ 271 stringConfig: s.AddConfigCharm(c, "wordpress", stringConfig, 1), 272 emptyConfig: s.AddConfigCharm(c, "wordpress", emptyConfig, 2), 273 floatConfig: s.AddConfigCharm(c, "wordpress", floatConfig, 3), 274 newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4), 275 } 276 277 for i, t := range setCharmConfigTests { 278 c.Logf("test %d: %s", i, t.summary) 279 280 origCh := charms[t.startconfig] 281 svc := s.AddTestingService(c, "wordpress", origCh) 282 err := svc.UpdateConfigSettings(t.startvalues) 283 c.Assert(err, gc.IsNil) 284 285 newCh := charms[t.endconfig] 286 err = svc.SetCharm(newCh, false) 287 var expectVals charm.Settings 288 var expectCh *state.Charm 289 if t.err != "" { 290 c.Assert(err, gc.ErrorMatches, t.err) 291 expectCh = origCh 292 expectVals = t.startvalues 293 } else { 294 c.Assert(err, gc.IsNil) 295 expectCh = newCh 296 expectVals = t.endvalues 297 } 298 299 sch, _, err := svc.Charm() 300 c.Assert(err, gc.IsNil) 301 c.Assert(sch.URL(), gc.DeepEquals, expectCh.URL()) 302 settings, err := svc.ConfigSettings() 303 c.Assert(err, gc.IsNil) 304 if len(expectVals) == 0 { 305 c.Assert(settings, gc.HasLen, 0) 306 } else { 307 c.Assert(settings, gc.DeepEquals, expectVals) 308 } 309 310 err = svc.Destroy() 311 c.Assert(err, gc.IsNil) 312 } 313 } 314 315 var serviceUpdateConfigSettingsTests = []struct { 316 about string 317 initial charm.Settings 318 update charm.Settings 319 expect charm.Settings 320 err string 321 }{{ 322 about: "unknown option", 323 update: charm.Settings{"foo": "bar"}, 324 err: `unknown option "foo"`, 325 }, { 326 about: "bad type", 327 update: charm.Settings{"skill-level": "profound"}, 328 err: `option "skill-level" expected int, got "profound"`, 329 }, { 330 about: "set string", 331 update: charm.Settings{"outlook": "positive"}, 332 expect: charm.Settings{"outlook": "positive"}, 333 }, { 334 about: "unset string and set another", 335 initial: charm.Settings{"outlook": "positive"}, 336 update: charm.Settings{"outlook": nil, "title": "sir"}, 337 expect: charm.Settings{"title": "sir"}, 338 }, { 339 about: "unset missing string", 340 update: charm.Settings{"outlook": nil}, 341 }, { 342 about: `empty strings are valid`, 343 initial: charm.Settings{"outlook": "positive"}, 344 update: charm.Settings{"outlook": "", "title": ""}, 345 expect: charm.Settings{"outlook": "", "title": ""}, 346 }, { 347 about: "preserve existing value", 348 initial: charm.Settings{"title": "sir"}, 349 update: charm.Settings{"username": "admin001"}, 350 expect: charm.Settings{"username": "admin001", "title": "sir"}, 351 }, { 352 about: "unset a default value, set a different default", 353 initial: charm.Settings{"username": "admin001", "title": "sir"}, 354 update: charm.Settings{"username": nil, "title": "My Title"}, 355 expect: charm.Settings{"title": "My Title"}, 356 }, { 357 about: "non-string type", 358 update: charm.Settings{"skill-level": 303}, 359 expect: charm.Settings{"skill-level": int64(303)}, 360 }, { 361 about: "unset non-string type", 362 initial: charm.Settings{"skill-level": 303}, 363 update: charm.Settings{"skill-level": nil}, 364 }} 365 366 func (s *ServiceSuite) TestUpdateConfigSettings(c *gc.C) { 367 sch := s.AddTestingCharm(c, "dummy") 368 for i, t := range serviceUpdateConfigSettingsTests { 369 c.Logf("test %d. %s", i, t.about) 370 svc := s.AddTestingService(c, "dummy-service", sch) 371 if t.initial != nil { 372 err := svc.UpdateConfigSettings(t.initial) 373 c.Assert(err, gc.IsNil) 374 } 375 err := svc.UpdateConfigSettings(t.update) 376 if t.err != "" { 377 c.Assert(err, gc.ErrorMatches, t.err) 378 } else { 379 c.Assert(err, gc.IsNil) 380 settings, err := svc.ConfigSettings() 381 c.Assert(err, gc.IsNil) 382 expect := t.expect 383 if expect == nil { 384 expect = charm.Settings{} 385 } 386 c.Assert(settings, gc.DeepEquals, expect) 387 } 388 err = svc.Destroy() 389 c.Assert(err, gc.IsNil) 390 } 391 } 392 393 func (s *ServiceSuite) TestSettingsRefCountWorks(c *gc.C) { 394 oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1) 395 newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2) 396 svcName := "mywp" 397 398 assertNoRef := func(sch *state.Charm) { 399 _, err := state.ServiceSettingsRefCount(s.State, svcName, sch.URL()) 400 c.Assert(errgo.Cause(err), gc.Equals, mgo.ErrNotFound) 401 } 402 assertRef := func(sch *state.Charm, refcount int) { 403 rc, err := state.ServiceSettingsRefCount(s.State, svcName, sch.URL()) 404 c.Assert(err, gc.IsNil) 405 c.Assert(rc, gc.Equals, refcount) 406 } 407 408 assertNoRef(oldCh) 409 assertNoRef(newCh) 410 411 svc := s.AddTestingService(c, svcName, oldCh) 412 assertRef(oldCh, 1) 413 assertNoRef(newCh) 414 415 err := svc.SetCharm(oldCh, false) 416 c.Assert(err, gc.IsNil) 417 assertRef(oldCh, 1) 418 assertNoRef(newCh) 419 420 err = svc.SetCharm(newCh, false) 421 c.Assert(err, gc.IsNil) 422 assertNoRef(oldCh) 423 assertRef(newCh, 1) 424 425 err = svc.SetCharm(oldCh, false) 426 c.Assert(err, gc.IsNil) 427 assertRef(oldCh, 1) 428 assertNoRef(newCh) 429 430 u, err := svc.AddUnit() 431 c.Assert(err, gc.IsNil) 432 curl, ok := u.CharmURL() 433 c.Assert(ok, gc.Equals, false) 434 assertRef(oldCh, 1) 435 assertNoRef(newCh) 436 437 err = u.SetCharmURL(oldCh.URL()) 438 c.Assert(err, gc.IsNil) 439 curl, ok = u.CharmURL() 440 c.Assert(ok, gc.Equals, true) 441 c.Assert(curl, gc.DeepEquals, oldCh.URL()) 442 assertRef(oldCh, 2) 443 assertNoRef(newCh) 444 445 err = u.EnsureDead() 446 c.Assert(err, gc.IsNil) 447 assertRef(oldCh, 2) 448 assertNoRef(newCh) 449 450 err = u.Remove() 451 c.Assert(err, gc.IsNil) 452 assertRef(oldCh, 1) 453 assertNoRef(newCh) 454 455 err = svc.Destroy() 456 c.Assert(err, gc.IsNil) 457 assertNoRef(oldCh) 458 assertNoRef(newCh) 459 } 460 461 const mysqlBaseMeta = ` 462 name: mysql 463 summary: "Database engine" 464 description: "A pretty popular database" 465 provides: 466 server: mysql 467 ` 468 const onePeerMeta = ` 469 peers: 470 cluster: mysql 471 ` 472 const twoPeersMeta = ` 473 peers: 474 cluster: mysql 475 loadbalancer: phony 476 ` 477 478 func (s *ServiceSuite) assertServiceRelations(c *gc.C, svc *state.Service, expectedKeys ...string) []*state.Relation { 479 rels, err := svc.Relations() 480 c.Assert(err, gc.IsNil) 481 if len(rels) == 0 { 482 return nil 483 } 484 relKeys := make([]string, len(expectedKeys)) 485 for i, rel := range rels { 486 relKeys[i] = rel.String() 487 } 488 sort.Strings(relKeys) 489 c.Assert(relKeys, gc.DeepEquals, expectedKeys) 490 return rels 491 } 492 493 func (s *ServiceSuite) TestNewPeerRelationsAddedOnUpgrade(c *gc.C) { 494 // Original mysql charm has no peer relations. 495 oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2) 496 newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3) 497 498 // No relations joined yet. 499 s.assertServiceRelations(c, s.mysql) 500 501 err := s.mysql.SetCharm(oldCh, false) 502 c.Assert(err, gc.IsNil) 503 s.assertServiceRelations(c, s.mysql, "mysql:cluster") 504 505 err = s.mysql.SetCharm(newCh, false) 506 c.Assert(err, gc.IsNil) 507 rels := s.assertServiceRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer") 508 509 // Check state consistency by attempting to destroy the service. 510 err = s.mysql.Destroy() 511 c.Assert(err, gc.IsNil) 512 513 // Check the peer relations got destroyed as well. 514 for _, rel := range rels { 515 err = rel.Refresh() 516 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 517 } 518 } 519 520 func jujuInfoEp(serviceName string) state.Endpoint { 521 return state.Endpoint{ 522 ServiceName: serviceName, 523 Relation: charm.Relation{ 524 Interface: "juju-info", 525 Name: "juju-info", 526 Role: charm.RoleProvider, 527 Scope: charm.ScopeGlobal, 528 }, 529 } 530 } 531 532 func (s *ServiceSuite) TestTag(c *gc.C) { 533 c.Assert(s.mysql.Tag(), gc.Equals, "service-mysql") 534 } 535 536 func (s *ServiceSuite) TestMysqlEndpoints(c *gc.C) { 537 _, err := s.mysql.Endpoint("mysql") 538 c.Assert(err, gc.ErrorMatches, `service "mysql" has no "mysql" relation`) 539 540 jiEP, err := s.mysql.Endpoint("juju-info") 541 c.Assert(err, gc.IsNil) 542 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("mysql")) 543 544 serverEP, err := s.mysql.Endpoint("server") 545 c.Assert(err, gc.IsNil) 546 c.Assert(serverEP, gc.DeepEquals, state.Endpoint{ 547 ServiceName: "mysql", 548 Relation: charm.Relation{ 549 Interface: "mysql", 550 Name: "server", 551 Role: charm.RoleProvider, 552 Scope: charm.ScopeGlobal, 553 }, 554 }) 555 556 eps, err := s.mysql.Endpoints() 557 c.Assert(err, gc.IsNil) 558 c.Assert(eps, gc.DeepEquals, []state.Endpoint{jiEP, serverEP}) 559 } 560 561 func (s *ServiceSuite) TestRiakEndpoints(c *gc.C) { 562 riak := s.AddTestingService(c, "myriak", s.AddTestingCharm(c, "riak")) 563 564 _, err := riak.Endpoint("garble") 565 c.Assert(err, gc.ErrorMatches, `service "myriak" has no "garble" relation`) 566 567 jiEP, err := riak.Endpoint("juju-info") 568 c.Assert(err, gc.IsNil) 569 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("myriak")) 570 571 ringEP, err := riak.Endpoint("ring") 572 c.Assert(err, gc.IsNil) 573 c.Assert(ringEP, gc.DeepEquals, state.Endpoint{ 574 ServiceName: "myriak", 575 Relation: charm.Relation{ 576 Interface: "riak", 577 Name: "ring", 578 Role: charm.RolePeer, 579 Scope: charm.ScopeGlobal, 580 Limit: 1, 581 }, 582 }) 583 584 adminEP, err := riak.Endpoint("admin") 585 c.Assert(err, gc.IsNil) 586 c.Assert(adminEP, gc.DeepEquals, state.Endpoint{ 587 ServiceName: "myriak", 588 Relation: charm.Relation{ 589 Interface: "http", 590 Name: "admin", 591 Role: charm.RoleProvider, 592 Scope: charm.ScopeGlobal, 593 }, 594 }) 595 596 endpointEP, err := riak.Endpoint("endpoint") 597 c.Assert(err, gc.IsNil) 598 c.Assert(endpointEP, gc.DeepEquals, state.Endpoint{ 599 ServiceName: "myriak", 600 Relation: charm.Relation{ 601 Interface: "http", 602 Name: "endpoint", 603 Role: charm.RoleProvider, 604 Scope: charm.ScopeGlobal, 605 }, 606 }) 607 608 eps, err := riak.Endpoints() 609 c.Assert(err, gc.IsNil) 610 c.Assert(eps, gc.DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP}) 611 } 612 613 func (s *ServiceSuite) TestWordpressEndpoints(c *gc.C) { 614 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 615 616 _, err := wordpress.Endpoint("nonsense") 617 c.Assert(err, gc.ErrorMatches, `service "wordpress" has no "nonsense" relation`) 618 619 jiEP, err := wordpress.Endpoint("juju-info") 620 c.Assert(err, gc.IsNil) 621 c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("wordpress")) 622 623 urlEP, err := wordpress.Endpoint("url") 624 c.Assert(err, gc.IsNil) 625 c.Assert(urlEP, gc.DeepEquals, state.Endpoint{ 626 ServiceName: "wordpress", 627 Relation: charm.Relation{ 628 Interface: "http", 629 Name: "url", 630 Role: charm.RoleProvider, 631 Scope: charm.ScopeGlobal, 632 }, 633 }) 634 635 ldEP, err := wordpress.Endpoint("logging-dir") 636 c.Assert(err, gc.IsNil) 637 c.Assert(ldEP, gc.DeepEquals, state.Endpoint{ 638 ServiceName: "wordpress", 639 Relation: charm.Relation{ 640 Interface: "logging", 641 Name: "logging-dir", 642 Role: charm.RoleProvider, 643 Scope: charm.ScopeContainer, 644 }, 645 }) 646 647 mpEP, err := wordpress.Endpoint("monitoring-port") 648 c.Assert(err, gc.IsNil) 649 c.Assert(mpEP, gc.DeepEquals, state.Endpoint{ 650 ServiceName: "wordpress", 651 Relation: charm.Relation{ 652 Interface: "monitoring", 653 Name: "monitoring-port", 654 Role: charm.RoleProvider, 655 Scope: charm.ScopeContainer, 656 }, 657 }) 658 659 dbEP, err := wordpress.Endpoint("db") 660 c.Assert(err, gc.IsNil) 661 c.Assert(dbEP, gc.DeepEquals, state.Endpoint{ 662 ServiceName: "wordpress", 663 Relation: charm.Relation{ 664 Interface: "mysql", 665 Name: "db", 666 Role: charm.RoleRequirer, 667 Scope: charm.ScopeGlobal, 668 Limit: 1, 669 }, 670 }) 671 672 cacheEP, err := wordpress.Endpoint("cache") 673 c.Assert(err, gc.IsNil) 674 c.Assert(cacheEP, gc.DeepEquals, state.Endpoint{ 675 ServiceName: "wordpress", 676 Relation: charm.Relation{ 677 Interface: "varnish", 678 Name: "cache", 679 Role: charm.RoleRequirer, 680 Scope: charm.ScopeGlobal, 681 Limit: 2, 682 Optional: true, 683 }, 684 }) 685 686 eps, err := wordpress.Endpoints() 687 c.Assert(err, gc.IsNil) 688 c.Assert(eps, gc.DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP}) 689 } 690 691 func (s *ServiceSuite) TestServiceRefresh(c *gc.C) { 692 s1, err := s.State.Service(s.mysql.Name()) 693 c.Assert(err, gc.IsNil) 694 695 err = s.mysql.SetCharm(s.charm, true) 696 c.Assert(err, gc.IsNil) 697 698 testch, force, err := s1.Charm() 699 c.Assert(err, gc.IsNil) 700 c.Assert(force, gc.Equals, false) 701 c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL()) 702 703 err = s1.Refresh() 704 c.Assert(err, gc.IsNil) 705 testch, force, err = s1.Charm() 706 c.Assert(err, gc.IsNil) 707 c.Assert(force, gc.Equals, true) 708 c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL()) 709 710 err = s.mysql.Destroy() 711 c.Assert(err, gc.IsNil) 712 err = s.mysql.Refresh() 713 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 714 } 715 716 func (s *ServiceSuite) TestServiceExposed(c *gc.C) { 717 // Check that querying for the exposed flag works correctly. 718 c.Assert(s.mysql.IsExposed(), gc.Equals, false) 719 720 // Check that setting and clearing the exposed flag works correctly. 721 err := s.mysql.SetExposed() 722 c.Assert(err, gc.IsNil) 723 c.Assert(s.mysql.IsExposed(), gc.Equals, true) 724 err = s.mysql.ClearExposed() 725 c.Assert(err, gc.IsNil) 726 c.Assert(s.mysql.IsExposed(), gc.Equals, false) 727 728 // Check that setting and clearing the exposed flag repeatedly does not fail. 729 err = s.mysql.SetExposed() 730 c.Assert(err, gc.IsNil) 731 err = s.mysql.SetExposed() 732 c.Assert(err, gc.IsNil) 733 err = s.mysql.ClearExposed() 734 c.Assert(err, gc.IsNil) 735 err = s.mysql.ClearExposed() 736 c.Assert(err, gc.IsNil) 737 err = s.mysql.SetExposed() 738 c.Assert(err, gc.IsNil) 739 c.Assert(s.mysql.IsExposed(), gc.Equals, true) 740 741 // Make the service Dying and check that ClearExposed and SetExposed fail. 742 // TODO(fwereade): maybe service destruction should always unexpose? 743 u, err := s.mysql.AddUnit() 744 c.Assert(err, gc.IsNil) 745 err = s.mysql.Destroy() 746 c.Assert(err, gc.IsNil) 747 err = s.mysql.ClearExposed() 748 c.Assert(err, gc.ErrorMatches, notAliveErr) 749 err = s.mysql.SetExposed() 750 c.Assert(err, gc.ErrorMatches, notAliveErr) 751 752 // Remove the service and check that both fail. 753 err = u.EnsureDead() 754 c.Assert(err, gc.IsNil) 755 err = u.Remove() 756 c.Assert(err, gc.IsNil) 757 err = s.mysql.SetExposed() 758 c.Assert(err, gc.ErrorMatches, notAliveErr) 759 err = s.mysql.ClearExposed() 760 c.Assert(err, gc.ErrorMatches, notAliveErr) 761 } 762 763 func (s *ServiceSuite) TestAddUnit(c *gc.C) { 764 // Check that principal units can be added on their own. 765 unitZero, err := s.mysql.AddUnit() 766 c.Assert(err, gc.IsNil) 767 c.Assert(unitZero.Name(), gc.Equals, "mysql/0") 768 c.Assert(unitZero.IsPrincipal(), gc.Equals, true) 769 c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0) 770 unitOne, err := s.mysql.AddUnit() 771 c.Assert(err, gc.IsNil) 772 c.Assert(unitOne.Name(), gc.Equals, "mysql/1") 773 c.Assert(unitOne.IsPrincipal(), gc.Equals, true) 774 c.Assert(unitOne.SubordinateNames(), gc.HasLen, 0) 775 776 // Assign the principal unit to a machine. 777 m, err := s.State.AddMachine("quantal", state.JobHostUnits) 778 c.Assert(err, gc.IsNil) 779 err = unitZero.AssignToMachine(m) 780 c.Assert(err, gc.IsNil) 781 782 // Add a subordinate service and check that units cannot be added directly. 783 // to add a subordinate unit. 784 subCharm := s.AddTestingCharm(c, "logging") 785 logging := s.AddTestingService(c, "logging", subCharm) 786 _, err = logging.AddUnit() 787 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "logging": service is a subordinate`) 788 789 // Indirectly create a subordinate unit by adding a relation and entering 790 // scope as a principal. 791 eps, err := s.State.InferEndpoints([]string{"logging", "mysql"}) 792 c.Assert(err, gc.IsNil) 793 rel, err := s.State.AddRelation(eps...) 794 c.Assert(err, gc.IsNil) 795 ru, err := rel.Unit(unitZero) 796 c.Assert(err, gc.IsNil) 797 err = ru.EnterScope(nil) 798 c.Assert(err, gc.IsNil) 799 subZero, err := s.State.Unit("logging/0") 800 c.Assert(err, gc.IsNil) 801 802 // Check that once it's refreshed unitZero has subordinates. 803 err = unitZero.Refresh() 804 c.Assert(err, gc.IsNil) 805 c.Assert(unitZero.SubordinateNames(), gc.DeepEquals, []string{"logging/0"}) 806 807 // Check the subordinate unit has been assigned its principal's machine. 808 id, err := subZero.AssignedMachineId() 809 c.Assert(err, gc.IsNil) 810 c.Assert(id, gc.Equals, m.Id()) 811 } 812 813 func (s *ServiceSuite) TestAddUnitWhenNotAlive(c *gc.C) { 814 u, err := s.mysql.AddUnit() 815 c.Assert(err, gc.IsNil) 816 err = s.mysql.Destroy() 817 c.Assert(err, gc.IsNil) 818 _, err = s.mysql.AddUnit() 819 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is not alive`) 820 err = u.EnsureDead() 821 c.Assert(err, gc.IsNil) 822 err = u.Remove() 823 c.Assert(err, gc.IsNil) 824 _, err = s.mysql.AddUnit() 825 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service "mysql" not found`) 826 } 827 828 func (s *ServiceSuite) TestReadUnit(c *gc.C) { 829 _, err := s.mysql.AddUnit() 830 c.Assert(err, gc.IsNil) 831 _, err = s.mysql.AddUnit() 832 c.Assert(err, gc.IsNil) 833 834 // Check that retrieving a unit from the service works correctly. 835 unit, err := s.mysql.Unit("mysql/0") 836 c.Assert(err, gc.IsNil) 837 c.Assert(unit.Name(), gc.Equals, "mysql/0") 838 839 // Check that retrieving a unit from state works correctly. 840 unit, err = s.State.Unit("mysql/0") 841 c.Assert(err, gc.IsNil) 842 c.Assert(unit.Name(), gc.Equals, "mysql/0") 843 844 // Check that retrieving a non-existent or an invalidly 845 // named unit fail nicely. 846 unit, err = s.mysql.Unit("mysql") 847 c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`) 848 unit, err = s.mysql.Unit("mysql/0/0") 849 c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`) 850 unit, err = s.mysql.Unit("pressword/0") 851 c.Assert(err, gc.ErrorMatches, `cannot get unit "pressword/0" from service "mysql": .*`) 852 853 // Check direct state retrieval also fails nicely. 854 unit, err = s.State.Unit("mysql") 855 c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`) 856 unit, err = s.State.Unit("mysql/0/0") 857 c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`) 858 unit, err = s.State.Unit("pressword/0") 859 c.Assert(err, gc.ErrorMatches, `unit "pressword/0" not found`) 860 861 // Add another service to check units are not misattributed. 862 mysql := s.AddTestingService(c, "wordpress", s.charm) 863 _, err = mysql.AddUnit() 864 c.Assert(err, gc.IsNil) 865 866 // BUG(aram): use error strings from state. 867 unit, err = s.mysql.Unit("wordpress/0") 868 c.Assert(err, gc.ErrorMatches, `cannot get unit "wordpress/0" from service "mysql": .*`) 869 870 units, err := s.mysql.AllUnits() 871 c.Assert(err, gc.IsNil) 872 c.Assert(sortedUnitNames(units), gc.DeepEquals, []string{"mysql/0", "mysql/1"}) 873 } 874 875 func (s *ServiceSuite) TestReadUnitWhenDying(c *gc.C) { 876 // Test that we can still read units when the service is Dying... 877 unit, err := s.mysql.AddUnit() 878 c.Assert(err, gc.IsNil) 879 preventUnitDestroyRemove(c, unit) 880 err = s.mysql.Destroy() 881 c.Assert(err, gc.IsNil) 882 _, err = s.mysql.AllUnits() 883 c.Assert(err, gc.IsNil) 884 _, err = s.mysql.Unit("mysql/0") 885 c.Assert(err, gc.IsNil) 886 887 // ...and when those units are Dying or Dead... 888 testWhenDying(c, unit, noErr, noErr, func() error { 889 _, err := s.mysql.AllUnits() 890 return err 891 }, func() error { 892 _, err := s.mysql.Unit("mysql/0") 893 return err 894 }) 895 896 // ...and even, in a very limited way, when the service itself is removed. 897 removeAllUnits(c, s.mysql) 898 _, err = s.mysql.AllUnits() 899 c.Assert(err, gc.IsNil) 900 } 901 902 func (s *ServiceSuite) TestDestroySimple(c *gc.C) { 903 err := s.mysql.Destroy() 904 c.Assert(err, gc.IsNil) 905 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 906 err = s.mysql.Refresh() 907 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 908 } 909 910 func (s *ServiceSuite) TestDestroyStillHasUnits(c *gc.C) { 911 unit, err := s.mysql.AddUnit() 912 c.Assert(err, gc.IsNil) 913 err = s.mysql.Destroy() 914 c.Assert(err, gc.IsNil) 915 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 916 917 err = unit.EnsureDead() 918 c.Assert(err, gc.IsNil) 919 err = s.mysql.Refresh() 920 c.Assert(err, gc.IsNil) 921 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 922 923 err = unit.Remove() 924 c.Assert(err, gc.IsNil) 925 err = s.mysql.Refresh() 926 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 927 } 928 929 func (s *ServiceSuite) TestDestroyOnceHadUnits(c *gc.C) { 930 unit, err := s.mysql.AddUnit() 931 c.Assert(err, gc.IsNil) 932 err = unit.EnsureDead() 933 c.Assert(err, gc.IsNil) 934 err = unit.Remove() 935 c.Assert(err, gc.IsNil) 936 937 err = s.mysql.Destroy() 938 c.Assert(err, gc.IsNil) 939 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 940 err = s.mysql.Refresh() 941 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 942 } 943 944 func (s *ServiceSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) { 945 unit, err := s.mysql.AddUnit() 946 c.Assert(err, gc.IsNil) 947 err = s.mysql.Refresh() 948 c.Assert(err, gc.IsNil) 949 err = unit.EnsureDead() 950 c.Assert(err, gc.IsNil) 951 err = unit.Remove() 952 c.Assert(err, gc.IsNil) 953 954 err = s.mysql.Destroy() 955 c.Assert(err, gc.IsNil) 956 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 957 err = s.mysql.Refresh() 958 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 959 } 960 961 func (s *ServiceSuite) TestDestroyStaleZeroUnitCount(c *gc.C) { 962 unit, err := s.mysql.AddUnit() 963 c.Assert(err, gc.IsNil) 964 965 err = s.mysql.Destroy() 966 c.Assert(err, gc.IsNil) 967 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 968 969 err = s.mysql.Refresh() 970 c.Assert(err, gc.IsNil) 971 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 972 973 err = unit.EnsureDead() 974 c.Assert(err, gc.IsNil) 975 err = s.mysql.Refresh() 976 c.Assert(err, gc.IsNil) 977 c.Assert(s.mysql.Life(), gc.Equals, state.Dying) 978 979 err = unit.Remove() 980 c.Assert(err, gc.IsNil) 981 err = s.mysql.Refresh() 982 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 983 } 984 985 func (s *ServiceSuite) TestDestroyWithRemovableRelation(c *gc.C) { 986 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 987 eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"}) 988 c.Assert(err, gc.IsNil) 989 rel, err := s.State.AddRelation(eps...) 990 c.Assert(err, gc.IsNil) 991 992 // Destroy a service with no units in relation scope; check service and 993 // unit removed. 994 err = wordpress.Destroy() 995 c.Assert(err, gc.IsNil) 996 err = wordpress.Refresh() 997 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 998 err = rel.Refresh() 999 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1000 } 1001 1002 func (s *ServiceSuite) TestDestroyWithReferencedRelation(c *gc.C) { 1003 s.assertDestroyWithReferencedRelation(c, true) 1004 } 1005 1006 func (s *ServiceSuite) TestDestroyWithreferencedRelationStaleCount(c *gc.C) { 1007 s.assertDestroyWithReferencedRelation(c, false) 1008 } 1009 1010 func (s *ServiceSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) { 1011 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1012 eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"}) 1013 c.Assert(err, gc.IsNil) 1014 rel0, err := s.State.AddRelation(eps...) 1015 c.Assert(err, gc.IsNil) 1016 1017 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 1018 eps, err = s.State.InferEndpoints([]string{"logging", "mysql"}) 1019 c.Assert(err, gc.IsNil) 1020 rel1, err := s.State.AddRelation(eps...) 1021 c.Assert(err, gc.IsNil) 1022 1023 // Add a separate reference to the first relation. 1024 unit, err := wordpress.AddUnit() 1025 c.Assert(err, gc.IsNil) 1026 ru, err := rel0.Unit(unit) 1027 c.Assert(err, gc.IsNil) 1028 err = ru.EnterScope(nil) 1029 c.Assert(err, gc.IsNil) 1030 1031 // Optionally update the service document to get correct relation counts. 1032 if refresh { 1033 err = s.mysql.Destroy() 1034 c.Assert(err, gc.IsNil) 1035 } 1036 1037 // Destroy, and check that the first relation becomes Dying... 1038 err = s.mysql.Destroy() 1039 c.Assert(err, gc.IsNil) 1040 err = rel0.Refresh() 1041 c.Assert(err, gc.IsNil) 1042 c.Assert(rel0.Life(), gc.Equals, state.Dying) 1043 1044 // ...while the second is removed directly. 1045 err = rel1.Refresh() 1046 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1047 1048 // Drop the last reference to the first relation; check the relation and 1049 // the service are are both removed. 1050 err = ru.LeaveScope() 1051 c.Assert(err, gc.IsNil) 1052 err = s.mysql.Refresh() 1053 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1054 err = rel0.Refresh() 1055 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1056 } 1057 1058 func (s *ServiceSuite) TestDestroyQueuesUnitCleanup(c *gc.C) { 1059 // Add 5 units; block quick-remove of mysql/1 and mysql/3 1060 units := make([]*state.Unit, 5) 1061 for i := range units { 1062 unit, err := s.mysql.AddUnit() 1063 c.Assert(err, gc.IsNil) 1064 units[i] = unit 1065 if i%2 != 0 { 1066 preventUnitDestroyRemove(c, unit) 1067 } 1068 } 1069 1070 // Check state is clean. 1071 dirty, err := s.State.NeedsCleanup() 1072 c.Assert(err, gc.IsNil) 1073 c.Assert(dirty, gc.Equals, false) 1074 1075 // Destroy mysql, and check units are not touched. 1076 err = s.mysql.Destroy() 1077 c.Assert(err, gc.IsNil) 1078 for _, unit := range units { 1079 assertLife(c, unit, state.Alive) 1080 } 1081 1082 // Check a cleanup doc was added. 1083 dirty, err = s.State.NeedsCleanup() 1084 c.Assert(err, gc.IsNil) 1085 c.Assert(dirty, gc.Equals, true) 1086 1087 // Run the cleanup and check the units. 1088 err = s.State.Cleanup() 1089 c.Assert(err, gc.IsNil) 1090 for i, unit := range units { 1091 if i%2 != 0 { 1092 assertLife(c, unit, state.Dying) 1093 } else { 1094 assertRemoved(c, unit) 1095 } 1096 } 1097 1098 // Check we're now clean. 1099 dirty, err = s.State.NeedsCleanup() 1100 c.Assert(err, gc.IsNil) 1101 c.Assert(dirty, gc.Equals, false) 1102 } 1103 1104 func (s *ServiceSuite) TestReadUnitWithChangingState(c *gc.C) { 1105 // Check that reading a unit after removing the service 1106 // fails nicely. 1107 err := s.mysql.Destroy() 1108 c.Assert(err, gc.IsNil) 1109 err = s.mysql.Refresh() 1110 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1111 _, err = s.State.Unit("mysql/0") 1112 c.Assert(err, gc.ErrorMatches, `unit "mysql/0" not found`) 1113 } 1114 1115 func uint64p(val uint64) *uint64 { 1116 return &val 1117 } 1118 1119 func (s *ServiceSuite) TestConstraints(c *gc.C) { 1120 // Constraints are initially empty (for now). 1121 cons, err := s.mysql.Constraints() 1122 c.Assert(err, gc.IsNil) 1123 c.Assert(&cons, jc.Satisfies, constraints.IsEmpty) 1124 1125 // Constraints can be set. 1126 cons2 := constraints.Value{Mem: uint64p(4096)} 1127 err = s.mysql.SetConstraints(cons2) 1128 cons3, err := s.mysql.Constraints() 1129 c.Assert(err, gc.IsNil) 1130 c.Assert(cons3, gc.DeepEquals, cons2) 1131 1132 // Constraints are completely overwritten when re-set. 1133 cons4 := constraints.Value{CpuPower: uint64p(750)} 1134 err = s.mysql.SetConstraints(cons4) 1135 c.Assert(err, gc.IsNil) 1136 cons5, err := s.mysql.Constraints() 1137 c.Assert(err, gc.IsNil) 1138 c.Assert(cons5, gc.DeepEquals, cons4) 1139 1140 // Destroy the existing service; there's no way to directly assert 1141 // that the constraints are deleted... 1142 err = s.mysql.Destroy() 1143 c.Assert(err, gc.IsNil) 1144 err = s.mysql.Refresh() 1145 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1146 1147 // ...but we can check that old constraints do not affect new services 1148 // with matching names. 1149 ch, _, err := s.mysql.Charm() 1150 c.Assert(err, gc.IsNil) 1151 mysql := s.AddTestingService(c, s.mysql.Name(), ch) 1152 cons6, err := mysql.Constraints() 1153 c.Assert(err, gc.IsNil) 1154 c.Assert(&cons6, jc.Satisfies, constraints.IsEmpty) 1155 } 1156 1157 func (s *ServiceSuite) TestConstraintsLifecycle(c *gc.C) { 1158 // Dying. 1159 unit, err := s.mysql.AddUnit() 1160 c.Assert(err, gc.IsNil) 1161 err = s.mysql.Destroy() 1162 c.Assert(err, gc.IsNil) 1163 cons1 := constraints.MustParse("mem=1G") 1164 err = s.mysql.SetConstraints(cons1) 1165 c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`) 1166 scons, err := s.mysql.Constraints() 1167 c.Assert(err, gc.IsNil) 1168 c.Assert(&scons, jc.Satisfies, constraints.IsEmpty) 1169 1170 // Removed (== Dead, for a service). 1171 err = unit.EnsureDead() 1172 c.Assert(err, gc.IsNil) 1173 err = unit.Remove() 1174 c.Assert(err, gc.IsNil) 1175 err = s.mysql.SetConstraints(cons1) 1176 c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`) 1177 _, err = s.mysql.Constraints() 1178 c.Assert(err, gc.ErrorMatches, `constraints not found`) 1179 } 1180 1181 func (s *ServiceSuite) TestSubordinateConstraints(c *gc.C) { 1182 loggingCh := s.AddTestingCharm(c, "logging") 1183 logging := s.AddTestingService(c, "logging", loggingCh) 1184 1185 _, err := logging.Constraints() 1186 c.Assert(errgo.Cause(err), gc.Equals, state.ErrSubordinateConstraints) 1187 1188 err = logging.SetConstraints(constraints.Value{}) 1189 c.Assert(errgo.Cause(err), gc.Equals, state.ErrSubordinateConstraints) 1190 } 1191 1192 func (s *ServiceSuite) TestWatchUnitsBulkEvents(c *gc.C) { 1193 // Alive unit... 1194 alive, err := s.mysql.AddUnit() 1195 c.Assert(err, gc.IsNil) 1196 1197 // Dying unit... 1198 dying, err := s.mysql.AddUnit() 1199 c.Assert(err, gc.IsNil) 1200 preventUnitDestroyRemove(c, dying) 1201 err = dying.Destroy() 1202 c.Assert(err, gc.IsNil) 1203 1204 // Dead unit... 1205 dead, err := s.mysql.AddUnit() 1206 c.Assert(err, gc.IsNil) 1207 preventUnitDestroyRemove(c, dead) 1208 err = dead.Destroy() 1209 c.Assert(err, gc.IsNil) 1210 err = dead.EnsureDead() 1211 c.Assert(err, gc.IsNil) 1212 1213 // Gone unit. 1214 gone, err := s.mysql.AddUnit() 1215 c.Assert(err, gc.IsNil) 1216 err = gone.Destroy() 1217 c.Assert(err, gc.IsNil) 1218 1219 // All except gone unit are reported in initial event. 1220 w := s.mysql.WatchUnits() 1221 defer testing.AssertStop(c, w) 1222 wc := testing.NewStringsWatcherC(c, s.State, w) 1223 wc.AssertChange(alive.Name(), dying.Name(), dead.Name()) 1224 wc.AssertNoChange() 1225 1226 // Remove them all; alive/dying changes reported; dead never mentioned again. 1227 err = alive.Destroy() 1228 c.Assert(err, gc.IsNil) 1229 err = dying.EnsureDead() 1230 c.Assert(err, gc.IsNil) 1231 err = dying.Remove() 1232 c.Assert(err, gc.IsNil) 1233 err = dead.Remove() 1234 c.Assert(err, gc.IsNil) 1235 wc.AssertChange(alive.Name(), dying.Name()) 1236 wc.AssertNoChange() 1237 } 1238 1239 func (s *ServiceSuite) TestWatchUnitsLifecycle(c *gc.C) { 1240 // Empty initial event when no units. 1241 w := s.mysql.WatchUnits() 1242 defer testing.AssertStop(c, w) 1243 wc := testing.NewStringsWatcherC(c, s.State, w) 1244 wc.AssertChange() 1245 wc.AssertNoChange() 1246 1247 // Create one unit, check one change. 1248 quick, err := s.mysql.AddUnit() 1249 c.Assert(err, gc.IsNil) 1250 wc.AssertChange(quick.Name()) 1251 wc.AssertNoChange() 1252 1253 // Destroy that unit (short-circuited to removal), check one change. 1254 err = quick.Destroy() 1255 c.Assert(err, gc.IsNil) 1256 wc.AssertChange(quick.Name()) 1257 wc.AssertNoChange() 1258 1259 // Create another, check one change. 1260 slow, err := s.mysql.AddUnit() 1261 c.Assert(err, gc.IsNil) 1262 wc.AssertChange(slow.Name()) 1263 wc.AssertNoChange() 1264 1265 // Change unit itself, no change. 1266 preventUnitDestroyRemove(c, slow) 1267 wc.AssertNoChange() 1268 1269 // Make unit Dying, change detected. 1270 err = slow.Destroy() 1271 c.Assert(err, gc.IsNil) 1272 wc.AssertChange(slow.Name()) 1273 wc.AssertNoChange() 1274 1275 // Make unit Dead, change detected. 1276 err = slow.EnsureDead() 1277 c.Assert(err, gc.IsNil) 1278 wc.AssertChange(slow.Name()) 1279 wc.AssertNoChange() 1280 1281 // Remove unit, final change not detected. 1282 err = slow.Remove() 1283 c.Assert(err, gc.IsNil) 1284 wc.AssertNoChange() 1285 } 1286 1287 func (s *ServiceSuite) TestWatchRelations(c *gc.C) { 1288 // TODO(fwereade) split this test up a bit. 1289 w := s.mysql.WatchRelations() 1290 defer testing.AssertStop(c, w) 1291 wc := testing.NewStringsWatcherC(c, s.State, w) 1292 wc.AssertChange() 1293 wc.AssertNoChange() 1294 1295 // Add a relation; check change. 1296 mysqlep, err := s.mysql.Endpoint("server") 1297 c.Assert(err, gc.IsNil) 1298 wpch := s.AddTestingCharm(c, "wordpress") 1299 wpi := 0 1300 addRelation := func() *state.Relation { 1301 name := fmt.Sprintf("wp%d", wpi) 1302 wpi++ 1303 wp := s.AddTestingService(c, name, wpch) 1304 wpep, err := wp.Endpoint("db") 1305 c.Assert(err, gc.IsNil) 1306 rel, err := s.State.AddRelation(mysqlep, wpep) 1307 c.Assert(err, gc.IsNil) 1308 return rel 1309 } 1310 rel0 := addRelation() 1311 wc.AssertChange(rel0.String()) 1312 wc.AssertNoChange() 1313 1314 // Add another relation; check change. 1315 rel1 := addRelation() 1316 wc.AssertChange(rel1.String()) 1317 wc.AssertNoChange() 1318 1319 // Destroy a relation; check change. 1320 err = rel0.Destroy() 1321 c.Assert(err, gc.IsNil) 1322 wc.AssertChange(rel0.String()) 1323 wc.AssertNoChange() 1324 1325 // Stop watcher; check change chan is closed. 1326 testing.AssertStop(c, w) 1327 wc.AssertClosed() 1328 1329 // Add a new relation; start a new watcher; check initial event. 1330 rel2 := addRelation() 1331 w = s.mysql.WatchRelations() 1332 defer testing.AssertStop(c, w) 1333 wc = testing.NewStringsWatcherC(c, s.State, w) 1334 wc.AssertChange(rel1.String(), rel2.String()) 1335 wc.AssertNoChange() 1336 1337 // Add a unit to the new relation; check no change. 1338 unit, err := s.mysql.AddUnit() 1339 c.Assert(err, gc.IsNil) 1340 ru2, err := rel2.Unit(unit) 1341 c.Assert(err, gc.IsNil) 1342 err = ru2.EnterScope(nil) 1343 c.Assert(err, gc.IsNil) 1344 wc.AssertNoChange() 1345 1346 // Destroy the relation with the unit in scope, and add another; check 1347 // changes. 1348 err = rel2.Destroy() 1349 c.Assert(err, gc.IsNil) 1350 rel3 := addRelation() 1351 wc.AssertChange(rel2.String(), rel3.String()) 1352 wc.AssertNoChange() 1353 1354 // Leave scope, destroying the relation, and check that change as well. 1355 err = ru2.LeaveScope() 1356 c.Assert(err, gc.IsNil) 1357 wc.AssertChange(rel2.String()) 1358 wc.AssertNoChange() 1359 } 1360 1361 func removeAllUnits(c *gc.C, s *state.Service) { 1362 us, err := s.AllUnits() 1363 c.Assert(err, gc.IsNil) 1364 for _, u := range us { 1365 err = u.EnsureDead() 1366 c.Assert(err, gc.IsNil) 1367 err = u.Remove() 1368 c.Assert(err, gc.IsNil) 1369 } 1370 } 1371 1372 func (s *ServiceSuite) TestWatchService(c *gc.C) { 1373 w := s.mysql.Watch() 1374 defer testing.AssertStop(c, w) 1375 1376 // Initial event. 1377 wc := testing.NewNotifyWatcherC(c, s.State, w) 1378 wc.AssertOneChange() 1379 1380 // Make one change (to a separate instance), check one event. 1381 service, err := s.State.Service(s.mysql.Name()) 1382 c.Assert(err, gc.IsNil) 1383 err = service.SetExposed() 1384 c.Assert(err, gc.IsNil) 1385 wc.AssertOneChange() 1386 1387 // Make two changes, check one event. 1388 err = service.ClearExposed() 1389 c.Assert(err, gc.IsNil) 1390 err = service.SetCharm(s.charm, true) 1391 c.Assert(err, gc.IsNil) 1392 wc.AssertOneChange() 1393 1394 // Stop, check closed. 1395 testing.AssertStop(c, w) 1396 wc.AssertClosed() 1397 1398 // Remove service, start new watch, check single event. 1399 err = service.Destroy() 1400 c.Assert(err, gc.IsNil) 1401 w = s.mysql.Watch() 1402 defer testing.AssertStop(c, w) 1403 testing.NewNotifyWatcherC(c, s.State, w).AssertOneChange() 1404 } 1405 1406 func (s *ServiceSuite) TestAnnotatorForService(c *gc.C) { 1407 testAnnotator(c, func() (state.Annotator, error) { 1408 return s.State.Service("mysql") 1409 }) 1410 } 1411 1412 func (s *ServiceSuite) TestAnnotationRemovalForService(c *gc.C) { 1413 annotations := map[string]string{"mykey": "myvalue"} 1414 err := s.mysql.SetAnnotations(annotations) 1415 c.Assert(err, gc.IsNil) 1416 err = s.mysql.Destroy() 1417 c.Assert(err, gc.IsNil) 1418 ann, err := s.mysql.Annotations() 1419 c.Assert(err, gc.IsNil) 1420 c.Assert(ann, gc.DeepEquals, make(map[string]string)) 1421 } 1422 1423 // SCHEMACHANGE 1424 // TODO(mattyw) remove when schema upgrades are possible 1425 // Check that GetOwnerTag returns user-admin even 1426 // when the service has no owner 1427 func (s *ServiceSuite) TestOwnerTagSchemaProtection(c *gc.C) { 1428 service := s.AddTestingService(c, "foobar", s.charm) 1429 state.SetServiceOwnerTag(service, "") 1430 c.Assert(state.GetServiceOwnerTag(service), gc.Equals, "") 1431 c.Assert(service.GetOwnerTag(), gc.Equals, "user-admin") 1432 }