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