6
. "launchpad.net/gocheck"
7
"launchpad.net/juju-core/charm"
8
"launchpad.net/juju-core/constraints"
9
"launchpad.net/juju-core/state"
14
type ServiceSuite struct {
20
var _ = Suite(&ServiceSuite{})
22
func (s *ServiceSuite) SetUpTest(c *C) {
23
s.ConnSuite.SetUpTest(c)
24
s.charm = s.AddTestingCharm(c, "mysql")
26
s.mysql, err = s.State.AddService("mysql", s.charm)
30
func (s *ServiceSuite) TestSetCharm(c *C) {
31
ch, force, err := s.mysql.Charm()
33
c.Assert(ch.URL(), DeepEquals, s.charm.URL())
34
c.Assert(force, Equals, false)
35
url, force := s.mysql.CharmURL()
36
c.Assert(url, DeepEquals, s.charm.URL())
37
c.Assert(force, Equals, false)
39
wp := s.AddTestingCharm(c, "wordpress")
40
err = s.mysql.SetCharm(wp, true)
41
ch, force, err1 := s.mysql.Charm()
43
c.Assert(ch.URL(), DeepEquals, wp.URL())
44
c.Assert(force, Equals, true)
45
url, force = s.mysql.CharmURL()
46
c.Assert(url, DeepEquals, wp.URL())
47
c.Assert(force, Equals, true)
49
// SetCharm fails when the service is Dying.
50
_, err = s.mysql.AddUnit()
52
err = s.mysql.Destroy()
54
err = s.mysql.SetCharm(wp, true)
55
c.Assert(err, ErrorMatches, `service "mysql" is not alive`)
58
func (s *ServiceSuite) TestSetCharmErrors(c *C) {
59
logging := s.AddTestingCharm(c, "logging")
60
err := s.mysql.SetCharm(logging, false)
61
c.Assert(err, ErrorMatches, "cannot change a service's subordinacy")
63
othermysql := s.AddSeriesCharm(c, "mysql", "otherseries")
64
err = s.mysql.SetCharm(othermysql, false)
65
c.Assert(err, ErrorMatches, "cannot change a service's series")
70
key: {default: My Key, description: Desc, type: string}
77
key: {default: 0.42, description: Float key, type: float}
79
var newStringConfig = `
81
key: {default: My Key, description: Desc, type: string}
82
other: {default: None, description: My Other, type: string}
85
var setCharmConfigTests = []struct {
88
startvalues map[string]interface{}
90
endvalues map[string]interface{}
94
summary: "add float key to empty config",
95
startconfig: emptyConfig,
96
endconfig: floatConfig,
98
summary: "add string key to empty config",
99
startconfig: emptyConfig,
100
endconfig: stringConfig,
102
summary: "add string key and preserve existing values",
103
startconfig: stringConfig,
104
startvalues: map[string]interface{}{"key": "foo", "other": "bar"},
105
endconfig: newStringConfig,
106
endvalues: map[string]interface{}{"key": "foo", "other": "bar"},
108
summary: "remove string key",
109
startconfig: stringConfig,
110
startvalues: map[string]interface{}{"key": "value"},
111
endconfig: emptyConfig,
113
summary: "remove float key",
114
startconfig: floatConfig,
115
startvalues: map[string]interface{}{"key": 123.45},
116
endconfig: emptyConfig,
118
summary: "change key type without values",
119
startconfig: stringConfig,
120
endconfig: floatConfig,
122
summary: "change key type with values",
123
startconfig: stringConfig,
124
startvalues: map[string]interface{}{"key": "value"},
125
endconfig: floatConfig,
126
err: `unexpected type in service configuration "key"="value"; expected float`,
130
func (s *ServiceSuite) TestSetCharmConfig(c *C) {
131
charms := map[string]*state.Charm{
132
stringConfig: s.AddConfigCharm(c, "wordpress", stringConfig, 1),
133
emptyConfig: s.AddConfigCharm(c, "wordpress", emptyConfig, 2),
134
floatConfig: s.AddConfigCharm(c, "wordpress", floatConfig, 3),
135
newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4),
138
for i, t := range setCharmConfigTests {
139
c.Logf("test %d: %s", i, t.summary)
141
origCh := charms[t.startconfig]
142
svc, err := s.State.AddService("wordpress", origCh)
144
cfg, err := svc.Config()
146
cfg.Update(t.startvalues)
150
newCh := charms[t.endconfig]
151
err = svc.SetCharm(newCh, false)
152
var expectVals map[string]interface{}
153
var expectCh *state.Charm
155
c.Assert(err, ErrorMatches, t.err)
157
expectVals = t.startvalues
161
expectVals = t.endvalues
164
sch, _, err := svc.Charm()
166
c.Assert(sch.URL(), DeepEquals, expectCh.URL())
167
cfg, err = svc.Config()
169
if len(expectVals) == 0 {
170
c.Assert(cfg.Map(), HasLen, 0)
172
c.Assert(cfg.Map(), DeepEquals, expectVals)
180
func serviceSet(options map[string]string) func(svc *state.Service) error {
181
return func(svc *state.Service) error {
182
return svc.SetConfig(options)
186
func serviceSetYAML(yaml string) func(svc *state.Service) error {
187
return func(svc *state.Service) error {
188
return svc.SetConfigYAML([]byte(yaml))
192
var serviceSetTests = []struct {
194
initial map[string]interface{}
195
set func(st *state.Service) error
196
expect map[string]interface{} // resulting configuration of the dummy service.
197
err string // error regex
199
about: "unknown option",
200
set: serviceSet(map[string]string{"foo": "bar"}),
201
err: `Unknown configuration option: "foo"`,
203
about: "set outlook",
204
set: serviceSet(map[string]string{"outlook": "positive"}),
205
expect: map[string]interface{}{
206
"outlook": "positive",
209
about: "unset outlook and set title",
210
initial: map[string]interface{}{
211
"outlook": "positive",
213
set: serviceSet(map[string]string{
218
expect: map[string]interface{}{
222
about: "set a default value",
223
initial: map[string]interface{}{
226
set: serviceSet(map[string]string{"username": "admin001"}),
227
expect: map[string]interface{}{
228
"username": "admin001",
232
about: "unset a default value, set a different default",
233
initial: map[string]interface{}{
234
"username": "admin001",
237
set: serviceSet(map[string]string{
242
expect: map[string]interface{}{
246
about: "bad configuration",
247
set: serviceSetYAML("345"),
248
err: "malformed YAML data",
250
about: "config with no options",
251
set: serviceSetYAML("{}"),
252
expect: map[string]interface{}{},
254
about: "set some attributes",
255
initial: map[string]interface{}{
258
set: serviceSetYAML("skill-level: 9000\nusername: admin001\n\n"),
259
expect: map[string]interface{}{
261
"username": "admin001",
262
"skill-level": int64(9000), // yaml int types are int64
265
about: "remove an attribute by setting to empty string",
266
initial: map[string]interface{}{
270
set: serviceSetYAML("title: ''\n"),
271
expect: map[string]interface{}{
277
func (s *ServiceSuite) TestSet(c *C) {
278
sch := s.AddTestingCharm(c, "dummy")
279
for i, t := range serviceSetTests {
280
c.Logf("test %d. %s", i, t.about)
281
svc, err := s.State.AddService("dummy-service", sch)
283
if t.initial != nil {
284
cfg, err := svc.Config()
286
cfg.Update(t.initial)
292
c.Assert(err, ErrorMatches, t.err)
295
cfg, err := svc.Config()
297
c.Assert(cfg.Map(), DeepEquals, t.expect)
304
func (s *ServiceSuite) TestSettingsRefCountWorks(c *C) {
305
oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
306
newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
309
assertNoRef := func(sch *state.Charm) {
310
_, err := state.ServiceSettingsRefCount(s.State, svcName, sch.URL())
311
c.Assert(err, Equals, mgo.ErrNotFound)
313
assertRef := func(sch *state.Charm, refcount int) {
314
rc, err := state.ServiceSettingsRefCount(s.State, svcName, sch.URL())
316
c.Assert(rc, Equals, refcount)
322
svc, err := s.State.AddService(svcName, oldCh)
327
err = svc.SetCharm(oldCh, false)
332
err = svc.SetCharm(newCh, false)
337
err = svc.SetCharm(oldCh, false)
342
u, err := svc.AddUnit()
344
curl, ok := u.CharmURL()
345
c.Assert(ok, Equals, false)
349
err = u.SetCharmURL(oldCh.URL())
351
curl, ok = u.CharmURL()
352
c.Assert(ok, Equals, true)
353
c.Assert(curl, DeepEquals, oldCh.URL())
373
const mysqlBaseMeta = `
375
summary: "Database engine"
376
description: "A pretty popular database"
380
const onePeerMeta = `
384
const twoPeersMeta = `
390
func (s *ServiceSuite) assertServiceRelations(c *C, svc *state.Service, expectedKeys ...string) []*state.Relation {
391
rels, err := svc.Relations()
396
relKeys := make([]string, len(expectedKeys))
397
for i, rel := range rels {
398
relKeys[i] = rel.String()
400
sort.Strings(relKeys)
401
c.Assert(relKeys, DeepEquals, expectedKeys)
405
func (s *ServiceSuite) TestNewPeerRelationsAddedOnUpgrade(c *C) {
406
// Original mysql charm has no peer relations.
407
oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2)
408
newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3)
410
// No relations joined yet.
411
s.assertServiceRelations(c, s.mysql)
413
err := s.mysql.SetCharm(oldCh, false)
415
s.assertServiceRelations(c, s.mysql, "mysql:cluster")
417
err = s.mysql.SetCharm(newCh, false)
419
rels := s.assertServiceRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer")
421
// Check state consistency by attempting to destroy the service.
422
err = s.mysql.Destroy()
425
// Check the peer relations got destroyed as well.
426
for _, rel := range rels {
428
c.Assert(state.IsNotFound(err), Equals, true)
432
func jujuInfoEp(serviceName string) state.Endpoint {
433
return state.Endpoint{
434
ServiceName: serviceName,
435
Relation: charm.Relation{
436
Interface: "juju-info",
438
Role: charm.RoleProvider,
439
Scope: charm.ScopeGlobal,
444
func (s *ServiceSuite) TestTag(c *C) {
445
c.Assert(s.mysql.Tag(), Equals, "service-mysql")
448
func (s *ServiceSuite) TestMysqlEndpoints(c *C) {
449
_, err := s.mysql.Endpoint("mysql")
450
c.Assert(err, ErrorMatches, `service "mysql" has no "mysql" relation`)
452
jiEP, err := s.mysql.Endpoint("juju-info")
454
c.Assert(jiEP, DeepEquals, jujuInfoEp("mysql"))
456
serverEP, err := s.mysql.Endpoint("server")
458
c.Assert(serverEP, DeepEquals, state.Endpoint{
459
ServiceName: "mysql",
460
Relation: charm.Relation{
463
Role: charm.RoleProvider,
464
Scope: charm.ScopeGlobal,
468
eps, err := s.mysql.Endpoints()
470
c.Assert(eps, DeepEquals, []state.Endpoint{jiEP, serverEP})
473
func (s *ServiceSuite) TestRiakEndpoints(c *C) {
474
riak, err := s.State.AddService("myriak", s.AddTestingCharm(c, "riak"))
477
_, err = riak.Endpoint("garble")
478
c.Assert(err, ErrorMatches, `service "myriak" has no "garble" relation`)
480
jiEP, err := riak.Endpoint("juju-info")
482
c.Assert(jiEP, DeepEquals, jujuInfoEp("myriak"))
484
ringEP, err := riak.Endpoint("ring")
486
c.Assert(ringEP, DeepEquals, state.Endpoint{
487
ServiceName: "myriak",
488
Relation: charm.Relation{
491
Role: charm.RolePeer,
492
Scope: charm.ScopeGlobal,
497
adminEP, err := riak.Endpoint("admin")
499
c.Assert(adminEP, DeepEquals, state.Endpoint{
500
ServiceName: "myriak",
501
Relation: charm.Relation{
504
Role: charm.RoleProvider,
505
Scope: charm.ScopeGlobal,
509
endpointEP, err := riak.Endpoint("endpoint")
511
c.Assert(endpointEP, DeepEquals, state.Endpoint{
512
ServiceName: "myriak",
513
Relation: charm.Relation{
516
Role: charm.RoleProvider,
517
Scope: charm.ScopeGlobal,
521
eps, err := riak.Endpoints()
523
c.Assert(eps, DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP})
526
func (s *ServiceSuite) TestWordpressEndpoints(c *C) {
527
wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
530
_, err = wordpress.Endpoint("nonsense")
531
c.Assert(err, ErrorMatches, `service "wordpress" has no "nonsense" relation`)
533
jiEP, err := wordpress.Endpoint("juju-info")
535
c.Assert(jiEP, DeepEquals, jujuInfoEp("wordpress"))
537
urlEP, err := wordpress.Endpoint("url")
539
c.Assert(urlEP, DeepEquals, state.Endpoint{
540
ServiceName: "wordpress",
541
Relation: charm.Relation{
544
Role: charm.RoleProvider,
545
Scope: charm.ScopeGlobal,
549
ldEP, err := wordpress.Endpoint("logging-dir")
551
c.Assert(ldEP, DeepEquals, state.Endpoint{
552
ServiceName: "wordpress",
553
Relation: charm.Relation{
554
Interface: "logging",
556
Role: charm.RoleProvider,
557
Scope: charm.ScopeContainer,
561
dbEP, err := wordpress.Endpoint("db")
563
c.Assert(dbEP, DeepEquals, state.Endpoint{
564
ServiceName: "wordpress",
565
Relation: charm.Relation{
568
Role: charm.RoleRequirer,
569
Scope: charm.ScopeGlobal,
574
cacheEP, err := wordpress.Endpoint("cache")
576
c.Assert(cacheEP, DeepEquals, state.Endpoint{
577
ServiceName: "wordpress",
578
Relation: charm.Relation{
579
Interface: "varnish",
581
Role: charm.RoleRequirer,
582
Scope: charm.ScopeGlobal,
588
eps, err := wordpress.Endpoints()
590
c.Assert(eps, DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, urlEP})
593
func (s *ServiceSuite) TestServiceRefresh(c *C) {
594
s1, err := s.State.Service(s.mysql.Name())
597
err = s.mysql.SetCharm(s.charm, true)
600
testch, force, err := s1.Charm()
602
c.Assert(force, Equals, false)
603
c.Assert(testch.URL(), DeepEquals, s.charm.URL())
607
testch, force, err = s1.Charm()
609
c.Assert(force, Equals, true)
610
c.Assert(testch.URL(), DeepEquals, s.charm.URL())
612
err = s.mysql.Destroy()
614
err = s.mysql.Refresh()
615
c.Assert(state.IsNotFound(err), Equals, true)
618
func (s *ServiceSuite) TestServiceExposed(c *C) {
619
// Check that querying for the exposed flag works correctly.
620
c.Assert(s.mysql.IsExposed(), Equals, false)
622
// Check that setting and clearing the exposed flag works correctly.
623
err := s.mysql.SetExposed()
625
c.Assert(s.mysql.IsExposed(), Equals, true)
626
err = s.mysql.ClearExposed()
628
c.Assert(s.mysql.IsExposed(), Equals, false)
630
// Check that setting and clearing the exposed flag repeatedly does not fail.
631
err = s.mysql.SetExposed()
633
err = s.mysql.SetExposed()
635
err = s.mysql.ClearExposed()
637
err = s.mysql.ClearExposed()
639
err = s.mysql.SetExposed()
641
c.Assert(s.mysql.IsExposed(), Equals, true)
643
// Make the service Dying and check that ClearExposed and SetExposed fail.
644
// TODO(fwereade): maybe service destruction should always unexpose?
645
u, err := s.mysql.AddUnit()
647
err = s.mysql.Destroy()
649
err = s.mysql.ClearExposed()
650
c.Assert(err, ErrorMatches, notAliveErr)
651
err = s.mysql.SetExposed()
652
c.Assert(err, ErrorMatches, notAliveErr)
654
// Remove the service and check that both fail.
659
err = s.mysql.SetExposed()
660
c.Assert(err, ErrorMatches, notAliveErr)
661
err = s.mysql.ClearExposed()
662
c.Assert(err, ErrorMatches, notAliveErr)
665
func (s *ServiceSuite) TestAddUnit(c *C) {
666
// Check that principal units can be added on their own.
667
unitZero, err := s.mysql.AddUnit()
669
c.Assert(unitZero.Name(), Equals, "mysql/0")
670
c.Assert(unitZero.IsPrincipal(), Equals, true)
671
c.Assert(unitZero.SubordinateNames(), HasLen, 0)
672
unitOne, err := s.mysql.AddUnit()
674
c.Assert(unitOne.Name(), Equals, "mysql/1")
675
c.Assert(unitOne.IsPrincipal(), Equals, true)
676
c.Assert(unitOne.SubordinateNames(), HasLen, 0)
678
// Assign the principal unit to a machine.
679
m, err := s.State.AddMachine("series", state.JobHostUnits)
681
err = unitZero.AssignToMachine(m)
684
// Add a subordinate service and check that units cannot be added directly.
685
// to add a subordinate unit.
686
subCharm := s.AddTestingCharm(c, "logging")
687
logging, err := s.State.AddService("logging", subCharm)
689
_, err = logging.AddUnit()
690
c.Assert(err, ErrorMatches, `cannot add unit to service "logging": service is a subordinate`)
692
// Indirectly create a subordinate unit by adding a relation and entering
693
// scope as a principal.
694
eps, err := s.State.InferEndpoints([]string{"logging", "mysql"})
696
rel, err := s.State.AddRelation(eps...)
698
ru, err := rel.Unit(unitZero)
700
err = ru.EnterScope(nil)
702
subZero, err := s.State.Unit("logging/0")
705
// Check that once it's refreshed unitZero has subordinates.
706
err = unitZero.Refresh()
708
c.Assert(unitZero.SubordinateNames(), DeepEquals, []string{"logging/0"})
710
// Check the subordinate unit has been assigned its principal's machine.
711
id, err := subZero.AssignedMachineId()
713
c.Assert(id, Equals, m.Id())
716
func (s *ServiceSuite) TestAddUnitWhenNotAlive(c *C) {
717
u, err := s.mysql.AddUnit()
719
err = s.mysql.Destroy()
721
_, err = s.mysql.AddUnit()
722
c.Assert(err, ErrorMatches, `cannot add unit to service "mysql": service is not alive`)
727
_, err = s.mysql.AddUnit()
728
c.Assert(err, ErrorMatches, `cannot add unit to service "mysql": service "mysql" not found`)
731
func (s *ServiceSuite) TestReadUnit(c *C) {
732
_, err := s.mysql.AddUnit()
734
_, err = s.mysql.AddUnit()
737
// Check that retrieving a unit from the service works correctly.
738
unit, err := s.mysql.Unit("mysql/0")
740
c.Assert(unit.Name(), Equals, "mysql/0")
742
// Check that retrieving a unit from state works correctly.
743
unit, err = s.State.Unit("mysql/0")
745
c.Assert(unit.Name(), Equals, "mysql/0")
747
// Check that retrieving a non-existent or an invalidly
748
// named unit fail nicely.
749
unit, err = s.mysql.Unit("mysql")
750
c.Assert(err, ErrorMatches, `"mysql" is not a valid unit name`)
751
unit, err = s.mysql.Unit("mysql/0/0")
752
c.Assert(err, ErrorMatches, `"mysql/0/0" is not a valid unit name`)
753
unit, err = s.mysql.Unit("pressword/0")
754
c.Assert(err, ErrorMatches, `cannot get unit "pressword/0" from service "mysql": .*`)
756
// Check direct state retrieval also fails nicely.
757
unit, err = s.State.Unit("mysql")
758
c.Assert(err, ErrorMatches, `"mysql" is not a valid unit name`)
759
unit, err = s.State.Unit("mysql/0/0")
760
c.Assert(err, ErrorMatches, `"mysql/0/0" is not a valid unit name`)
761
unit, err = s.State.Unit("pressword/0")
762
c.Assert(err, ErrorMatches, `unit "pressword/0" not found`)
764
// Add another service to check units are not misattributed.
765
mysql, err := s.State.AddService("wordpress", s.charm)
767
_, err = mysql.AddUnit()
770
// BUG(aram): use error strings from state.
771
unit, err = s.mysql.Unit("wordpress/0")
772
c.Assert(err, ErrorMatches, `cannot get unit "wordpress/0" from service "mysql": .*`)
774
units, err := s.mysql.AllUnits()
776
c.Assert(sortedUnitNames(units), DeepEquals, []string{"mysql/0", "mysql/1"})
779
func (s *ServiceSuite) TestReadUnitWhenDying(c *C) {
780
// Test that we can still read units when the service is Dying...
781
unit, err := s.mysql.AddUnit()
783
preventUnitDestroyRemove(c, s.State, unit)
784
err = s.mysql.Destroy()
786
_, err = s.mysql.AllUnits()
788
_, err = s.mysql.Unit("mysql/0")
791
// ...and when those units are Dying or Dead...
792
testWhenDying(c, unit, noErr, noErr, func() error {
793
_, err := s.mysql.AllUnits()
796
_, err := s.mysql.Unit("mysql/0")
800
// ...and even, in a very limited way, when the service itself is removed.
801
removeAllUnits(c, s.mysql)
802
_, err = s.mysql.AllUnits()
806
func (s *ServiceSuite) TestDestroySimple(c *C) {
807
err := s.mysql.Destroy()
809
c.Assert(s.mysql.Life(), Equals, state.Dying)
810
err = s.mysql.Refresh()
811
c.Assert(state.IsNotFound(err), Equals, true)
814
func (s *ServiceSuite) TestDestroyStillHasUnits(c *C) {
815
unit, err := s.mysql.AddUnit()
817
err = s.mysql.Destroy()
819
c.Assert(s.mysql.Life(), Equals, state.Dying)
821
err = unit.EnsureDead()
823
err = s.mysql.Refresh()
825
c.Assert(s.mysql.Life(), Equals, state.Dying)
829
err = s.mysql.Refresh()
830
c.Assert(state.IsNotFound(err), Equals, true)
833
func (s *ServiceSuite) TestDestroyOnceHadUnits(c *C) {
834
unit, err := s.mysql.AddUnit()
836
err = unit.EnsureDead()
841
err = s.mysql.Destroy()
843
c.Assert(s.mysql.Life(), Equals, state.Dying)
844
err = s.mysql.Refresh()
845
c.Assert(state.IsNotFound(err), Equals, true)
848
func (s *ServiceSuite) TestDestroyStaleNonZeroUnitCount(c *C) {
849
unit, err := s.mysql.AddUnit()
851
err = s.mysql.Refresh()
853
err = unit.EnsureDead()
858
err = s.mysql.Destroy()
860
c.Assert(s.mysql.Life(), Equals, state.Dying)
861
err = s.mysql.Refresh()
862
c.Assert(state.IsNotFound(err), Equals, true)
865
func (s *ServiceSuite) TestDestroyStaleZeroUnitCount(c *C) {
866
unit, err := s.mysql.AddUnit()
869
err = s.mysql.Destroy()
871
c.Assert(s.mysql.Life(), Equals, state.Dying)
873
err = s.mysql.Refresh()
875
c.Assert(s.mysql.Life(), Equals, state.Dying)
877
err = unit.EnsureDead()
879
err = s.mysql.Refresh()
881
c.Assert(s.mysql.Life(), Equals, state.Dying)
885
err = s.mysql.Refresh()
886
c.Assert(state.IsNotFound(err), Equals, true)
889
func (s *ServiceSuite) TestDestroyWithRemovableRelation(c *C) {
890
wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
892
eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
894
rel, err := s.State.AddRelation(eps...)
897
// Destroy a service with no units in relation scope; check service and
899
err = wordpress.Destroy()
901
err = wordpress.Refresh()
902
c.Assert(state.IsNotFound(err), Equals, true)
904
c.Assert(state.IsNotFound(err), Equals, true)
907
func (s *ServiceSuite) TestDestroyWithReferencedRelation(c *C) {
908
s.assertDestroyWithReferencedRelation(c, true)
911
func (s *ServiceSuite) TestDestroyWithreferencedRelationStaleCount(c *C) {
912
s.assertDestroyWithReferencedRelation(c, false)
915
func (s *ServiceSuite) assertDestroyWithReferencedRelation(c *C, refresh bool) {
916
wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
918
eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
920
rel0, err := s.State.AddRelation(eps...)
923
_, err = s.State.AddService("logging", s.AddTestingCharm(c, "logging"))
925
eps, err = s.State.InferEndpoints([]string{"logging", "mysql"})
927
rel1, err := s.State.AddRelation(eps...)
930
// Add a separate reference to the first relation.
931
unit, err := wordpress.AddUnit()
933
ru, err := rel0.Unit(unit)
935
err = ru.EnterScope(nil)
938
// Optionally update the service document to get correct relation counts.
940
err = s.mysql.Destroy()
944
// Destroy, and check that the first relation becomes Dying...
945
err = s.mysql.Destroy()
949
c.Assert(rel0.Life(), Equals, state.Dying)
951
// ...while the second is removed directly.
953
c.Assert(state.IsNotFound(err), Equals, true)
955
// Drop the last reference to the first relation; check the relation and
956
// the service are are both removed.
957
err = ru.LeaveScope()
959
err = s.mysql.Refresh()
960
c.Assert(state.IsNotFound(err), Equals, true)
962
c.Assert(state.IsNotFound(err), Equals, true)
965
func (s *ServiceSuite) TestReadUnitWithChangingState(c *C) {
966
// Check that reading a unit after removing the service
968
err := s.mysql.Destroy()
970
err = s.mysql.Refresh()
971
c.Assert(state.IsNotFound(err), Equals, true)
972
_, err = s.State.Unit("mysql/0")
973
c.Assert(err, ErrorMatches, `unit "mysql/0" not found`)
976
func (s *ServiceSuite) TestServiceConfig(c *C) {
977
env, err := s.mysql.Config()
981
c.Assert(env.Map(), DeepEquals, map[string]interface{}{})
983
env.Update(map[string]interface{}{"spam": "eggs", "eggs": "spam"})
984
env.Update(map[string]interface{}{"spam": "spam", "chaos": "emeralds"})
988
env, err = s.mysql.Config()
992
c.Assert(env.Map(), DeepEquals, map[string]interface{}{"spam": "spam", "eggs": "spam", "chaos": "emeralds"})
995
func uint64p(val uint64) *uint64 {
999
func (s *ServiceSuite) TestConstraints(c *C) {
1000
// Constraints are initially empty (for now).
1001
cons0 := constraints.Value{}
1002
cons1, err := s.mysql.Constraints()
1003
c.Assert(err, IsNil)
1004
c.Assert(cons1, DeepEquals, cons0)
1006
// Constraints can be set.
1007
cons2 := constraints.Value{Mem: uint64p(4096)}
1008
err = s.mysql.SetConstraints(cons2)
1009
cons3, err := s.mysql.Constraints()
1010
c.Assert(err, IsNil)
1011
c.Assert(cons3, DeepEquals, cons2)
1013
// Constraints are completely overwritten when re-set.
1014
cons4 := constraints.Value{CpuPower: uint64p(750)}
1015
err = s.mysql.SetConstraints(cons4)
1016
c.Assert(err, IsNil)
1017
cons5, err := s.mysql.Constraints()
1018
c.Assert(err, IsNil)
1019
c.Assert(cons5, DeepEquals, cons4)
1021
// Destroy the existing service; there's no way to directly assert
1022
// that the constraints are deleted...
1023
err = s.mysql.Destroy()
1024
c.Assert(err, IsNil)
1025
err = s.mysql.Refresh()
1026
c.Assert(state.IsNotFound(err), Equals, true)
1028
// ...but we can check that old constraints do not affect new services
1029
// with matching names.
1030
ch, _, err := s.mysql.Charm()
1031
c.Assert(err, IsNil)
1032
mysql, err := s.State.AddService(s.mysql.Name(), ch)
1033
c.Assert(err, IsNil)
1034
cons6, err := mysql.Constraints()
1035
c.Assert(err, IsNil)
1036
c.Assert(cons6, DeepEquals, cons0)
1039
func (s *ServiceSuite) TestConstraintsLifecycle(c *C) {
1041
unit, err := s.mysql.AddUnit()
1042
c.Assert(err, IsNil)
1043
err = s.mysql.Destroy()
1044
c.Assert(err, IsNil)
1045
cons1 := constraints.MustParse("mem=1G")
1046
err = s.mysql.SetConstraints(cons1)
1047
c.Assert(err, ErrorMatches, `cannot set constraints: not found or not alive`)
1048
scons, err := s.mysql.Constraints()
1049
c.Assert(err, IsNil)
1050
c.Assert(scons, DeepEquals, constraints.Value{})
1052
// Removed (== Dead, for a service).
1053
err = unit.EnsureDead()
1054
c.Assert(err, IsNil)
1056
c.Assert(err, IsNil)
1057
err = s.mysql.SetConstraints(cons1)
1058
c.Assert(err, ErrorMatches, `cannot set constraints: not found or not alive`)
1059
_, err = s.mysql.Constraints()
1060
c.Assert(err, ErrorMatches, `constraints not found`)
1063
func (s *ServiceSuite) TestSubordinateConstraints(c *C) {
1064
loggingCh := s.AddTestingCharm(c, "logging")
1065
logging, err := s.State.AddService("logging", loggingCh)
1066
c.Assert(err, IsNil)
1068
_, err = logging.Constraints()
1069
c.Assert(err, Equals, state.ErrSubordinateConstraints)
1071
err = logging.SetConstraints(constraints.Value{})
1072
c.Assert(err, Equals, state.ErrSubordinateConstraints)
1075
type unitSlice []*state.Unit
1077
func (m unitSlice) Len() int { return len(m) }
1078
func (m unitSlice) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
1079
func (m unitSlice) Less(i, j int) bool { return m[i].Name() < m[j].Name() }
1081
var serviceUnitsWatchTests = []struct {
1083
test func(*C, *state.State, *state.Service)
1087
"Check initial empty event",
1088
func(_ *C, _ *state.State, _ *state.Service) {},
1092
func(c *C, s *state.State, service *state.Service) {
1093
_, err := service.AddUnit()
1094
c.Assert(err, IsNil)
1096
[]string{"mysql/0"},
1098
"Add a unit, ignore unrelated change",
1099
func(c *C, s *state.State, service *state.Service) {
1100
_, err := service.AddUnit()
1101
c.Assert(err, IsNil)
1102
unit0, err := service.Unit("mysql/0")
1103
c.Assert(err, IsNil)
1104
err = unit0.SetPublicAddress("what.ever")
1105
c.Assert(err, IsNil)
1107
[]string{"mysql/1"},
1109
"Add two units at once",
1110
func(c *C, s *state.State, service *state.Service) {
1111
unit2, err := service.AddUnit()
1112
c.Assert(err, IsNil)
1113
preventUnitDestroyRemove(c, s, unit2)
1114
_, err = service.AddUnit()
1115
c.Assert(err, IsNil)
1117
[]string{"mysql/2", "mysql/3"},
1119
"Report dying unit",
1120
func(c *C, s *state.State, service *state.Service) {
1121
unit0, err := service.Unit("mysql/0")
1122
c.Assert(err, IsNil)
1123
preventUnitDestroyRemove(c, s, unit0)
1124
err = unit0.Destroy()
1125
c.Assert(err, IsNil)
1127
[]string{"mysql/0"},
1129
// I'm preserving these tests in amber, not fixing them.
1130
"Report another dying unit for no clear reason",
1131
func(c *C, s *state.State, service *state.Service) {
1132
unit2, err := service.Unit("mysql/2")
1133
c.Assert(err, IsNil)
1134
err = unit2.Destroy()
1135
c.Assert(err, IsNil)
1137
[]string{"mysql/2"},
1139
"Report multiple dead or dying units",
1140
func(c *C, s *state.State, service *state.Service) {
1141
unit0, err := service.Unit("mysql/0")
1142
c.Assert(err, IsNil)
1143
err = unit0.EnsureDead()
1144
c.Assert(err, IsNil)
1145
unit1, err := service.Unit("mysql/1")
1146
c.Assert(err, IsNil)
1147
err = unit1.EnsureDead()
1148
c.Assert(err, IsNil)
1150
[]string{"mysql/0", "mysql/1"},
1152
"Report dying unit along with a new, alive unit",
1153
func(c *C, s *state.State, service *state.Service) {
1154
unit3, err := service.Unit("mysql/3")
1155
c.Assert(err, IsNil)
1156
preventUnitDestroyRemove(c, s, unit3)
1157
err = unit3.Destroy()
1158
c.Assert(err, IsNil)
1159
_, err = service.AddUnit()
1160
c.Assert(err, IsNil)
1162
[]string{"mysql/3", "mysql/4"},
1164
"Report multiple dead units and multiple new, alive, units",
1165
func(c *C, s *state.State, service *state.Service) {
1166
unit3, err := service.Unit("mysql/3")
1167
c.Assert(err, IsNil)
1168
err = unit3.EnsureDead()
1169
c.Assert(err, IsNil)
1170
unit4, err := service.Unit("mysql/4")
1171
c.Assert(err, IsNil)
1172
err = unit4.EnsureDead()
1173
c.Assert(err, IsNil)
1174
_, err = service.AddUnit()
1175
c.Assert(err, IsNil)
1176
_, err = service.AddUnit()
1177
c.Assert(err, IsNil)
1179
[]string{"mysql/3", "mysql/4", "mysql/5", "mysql/6"},
1181
"Add many, and remove many at once",
1182
func(c *C, s *state.State, service *state.Service) {
1183
units := [20]*state.Unit{}
1185
for i := 0; i < len(units); i++ {
1186
units[i], err = service.AddUnit()
1187
c.Assert(err, IsNil)
1189
for i := 10; i < len(units); i++ {
1190
err = units[i].Destroy()
1191
c.Assert(err, IsNil)
1194
[]string{"mysql/10", "mysql/11", "mysql/12", "mysql/13", "mysql/14", "mysql/15", "mysql/16", "mysql/7", "mysql/8", "mysql/9"},
1196
"Change many at once",
1197
func(c *C, s *state.State, service *state.Service) {
1198
units := [10]*state.Unit{}
1200
for i := 0; i < len(units); i++ {
1201
units[i], err = service.Unit("mysql/" + fmt.Sprint(i+7))
1202
c.Assert(err, IsNil)
1204
for _, unit := range units {
1205
preventUnitDestroyRemove(c, s, unit)
1206
err = unit.Destroy()
1207
c.Assert(err, IsNil)
1209
err = units[8].EnsureDead()
1210
c.Assert(err, IsNil)
1211
err = units[9].EnsureDead()
1212
c.Assert(err, IsNil)
1214
[]string{"mysql/10", "mysql/11", "mysql/12", "mysql/13", "mysql/14", "mysql/15", "mysql/16", "mysql/7", "mysql/8", "mysql/9"},
1216
"Report dead when first seen and also add a new unit",
1217
func(c *C, s *state.State, service *state.Service) {
1218
unit, err := service.AddUnit()
1219
c.Assert(err, IsNil)
1220
err = unit.EnsureDead()
1221
c.Assert(err, IsNil)
1222
_, err = service.AddUnit()
1223
c.Assert(err, IsNil)
1225
[]string{"mysql/27", "mysql/28"},
1227
"report only units assigned to this machine",
1228
func(c *C, s *state.State, service *state.Service) {
1229
_, err := service.AddUnit()
1230
c.Assert(err, IsNil)
1231
_, err = service.AddUnit()
1232
c.Assert(err, IsNil)
1233
ch, _, err := service.Charm()
1234
c.Assert(err, IsNil)
1235
svc, err := s.AddService("bacon", ch)
1236
c.Assert(err, IsNil)
1237
_, err = svc.AddUnit()
1238
c.Assert(err, IsNil)
1239
_, err = svc.AddUnit()
1240
c.Assert(err, IsNil)
1241
unit10, err := service.Unit("mysql/10")
1242
c.Assert(err, IsNil)
1243
err = unit10.EnsureDead()
1244
c.Assert(err, IsNil)
1245
err = unit10.Remove()
1246
c.Assert(err, IsNil)
1248
[]string{"mysql/10", "mysql/29", "mysql/30"},
1250
"Report previously known machines that are removed",
1251
func(c *C, s *state.State, service *state.Service) {
1252
unit30, err := service.Unit("mysql/30")
1253
c.Assert(err, IsNil)
1254
err = unit30.EnsureDead()
1255
c.Assert(err, IsNil)
1256
err = unit30.Remove()
1257
c.Assert(err, IsNil)
1259
[]string{"mysql/30"},
1263
func (s *ServiceSuite) TestWatchUnits(c *C) {
1264
unitWatcher := s.mysql.WatchUnits()
1266
c.Assert(unitWatcher.Stop(), IsNil)
1269
for i, test := range serviceUnitsWatchTests {
1270
c.Logf("test %d: %s", i, test.summary)
1271
test.test(c, s.State, s.mysql)
1273
all = append(all, test.changes...)
1275
want := append([]string(nil), test.changes...)
1279
case new, ok := <-unitWatcher.Changes():
1280
c.Assert(ok, Equals, true)
1281
got = append(got, new...)
1282
if len(got) < len(want) {
1286
c.Assert(got, DeepEquals, want)
1287
case <-time.After(500 * time.Millisecond):
1288
c.Fatalf("did not get change, want: %#v", want)
1294
// Check that removing units for which we already got a Dead event
1295
// does not yield any more events.
1296
for _, uname := range all {
1297
unit, err := s.State.Unit(uname)
1298
if state.IsNotFound(err) || unit.Life() != state.Dead {
1301
c.Assert(err, IsNil)
1303
c.Assert(err, IsNil)
1307
case got := <-unitWatcher.Changes():
1308
c.Fatalf("got unexpected change: %#v", got)
1309
case <-time.After(100 * time.Millisecond):
1312
// Stop the watcher and restart it, check that it returns non-nil
1314
c.Assert(unitWatcher.Stop(), IsNil)
1315
unitWatcher = s.mysql.WatchUnits()
1317
want := []string{"mysql/11", "mysql/12", "mysql/13", "mysql/14", "mysql/2", "mysql/28", "mysql/29", "mysql/5", "mysql/6", "mysql/7", "mysql/8", "mysql/9"}
1319
case got, ok := <-unitWatcher.Changes():
1320
c.Assert(ok, Equals, true)
1322
c.Assert(got, DeepEquals, want)
1323
case <-time.After(500 * time.Millisecond):
1324
c.Fatalf("did not get change, want: %#v", want)
1327
// ignore unrelated change for non-alive unit.
1328
unit2, err := s.mysql.Unit("mysql/2")
1329
c.Assert(err, IsNil)
1330
err = unit2.SetPublicAddress("what.ever")
1331
c.Assert(err, IsNil)
1334
case got := <-unitWatcher.Changes():
1335
c.Fatalf("got unexpected change: %#v", got)
1336
case <-time.After(100 * time.Millisecond):
1340
func (s *ServiceSuite) TestWatchRelations(c *C) {
1341
w := s.mysql.WatchRelations()
1342
defer func() { c.Assert(w.Stop(), IsNil) }()
1344
assertNoChange := func() {
1347
case got := <-w.Changes():
1348
c.Fatalf("expected nothing, got %#v", got)
1349
case <-time.After(100 * time.Millisecond):
1352
assertChange := func(want ...int) {
1355
case got, ok := <-w.Changes():
1356
c.Assert(ok, Equals, true)
1358
c.Assert(got, HasLen, 0)
1362
c.Assert(got, DeepEquals, want)
1364
case <-time.After(500 * time.Millisecond):
1365
c.Fatalf("expected %#v, got nothing", want)
1370
// Check initial event, and lack of followup.
1373
// Add a relation; check change.
1374
mysqlep, err := s.mysql.Endpoint("server")
1375
c.Assert(err, IsNil)
1376
wpch := s.AddTestingCharm(c, "wordpress")
1378
addRelation := func() *state.Relation {
1379
name := fmt.Sprintf("wp%d", wpi)
1381
wp, err := s.State.AddService(name, wpch)
1382
c.Assert(err, IsNil)
1383
wpep, err := wp.Endpoint("db")
1384
c.Assert(err, IsNil)
1385
rel, err := s.State.AddRelation(mysqlep, wpep)
1386
c.Assert(err, IsNil)
1389
rel0 := addRelation()
1392
// Add another relation; check change.
1396
// Destroy a relation; check change.
1397
err = rel0.Destroy()
1398
c.Assert(err, IsNil)
1401
// Stop watcher; check change chan is closed.
1403
c.Assert(err, IsNil)
1404
assertClosed := func() {
1406
case _, ok := <-w.Changes():
1407
c.Assert(ok, Equals, false)
1409
c.Fatalf("Changes not closed")
1414
// Add a new relation; start a new watcher; check initial event.
1415
rel2 := addRelation()
1416
w = s.mysql.WatchRelations()
1419
// Add a unit to the new relation; check no change.
1420
unit, err := s.mysql.AddUnit()
1421
c.Assert(err, IsNil)
1422
ru2, err := rel2.Unit(unit)
1423
c.Assert(err, IsNil)
1424
err = ru2.EnterScope(nil)
1425
c.Assert(err, IsNil)
1428
// Destroy the relation with the unit in scope, and add another; check
1430
err = rel2.Destroy()
1431
c.Assert(err, IsNil)
1435
// Leave scope, destroying the relation, and check that change as well.
1436
err = ru2.LeaveScope()
1437
c.Assert(err, IsNil)
1441
func removeAllUnits(c *C, s *state.Service) {
1442
us, err := s.AllUnits()
1443
c.Assert(err, IsNil)
1444
for _, u := range us {
1445
err = u.EnsureDead()
1446
c.Assert(err, IsNil)
1448
c.Assert(err, IsNil)
1452
var watchServiceTests = []struct {
1453
test func(m *state.Service) error
1458
test: func(s *state.Service) error {
1459
return s.SetExposed()
1463
test: func(s *state.Service) error {
1464
return s.ClearExposed()
1468
test: func(s *state.Service) error {
1469
if _, err := s.AddUnit(); err != nil {
1478
func (s *ServiceSuite) TestWatchService(c *C) {
1479
altservice, err := s.State.Service(s.mysql.Name())
1480
c.Assert(err, IsNil)
1481
err = altservice.SetCharm(s.charm, true)
1482
c.Assert(err, IsNil)
1483
_, force, err := s.mysql.Charm()
1484
c.Assert(err, IsNil)
1485
c.Assert(force, Equals, false)
1487
w := s.mysql.Watch()
1489
c.Assert(w.Stop(), IsNil)
1493
case _, ok := <-w.Changes():
1494
c.Assert(ok, Equals, true)
1495
err := s.mysql.Refresh()
1496
c.Assert(err, IsNil)
1497
_, force, err := s.mysql.Charm()
1498
c.Assert(err, IsNil)
1499
c.Assert(force, Equals, true)
1500
case <-time.After(500 * time.Millisecond):
1501
c.Fatalf("did not get change: %v", s.mysql)
1504
for i, test := range watchServiceTests {
1505
c.Logf("test %d", i)
1506
err := test.test(altservice)
1507
c.Assert(err, IsNil)
1510
case _, ok := <-w.Changes():
1511
c.Assert(ok, Equals, true)
1512
err := s.mysql.Refresh()
1513
c.Assert(err, IsNil)
1514
c.Assert(s.mysql.Life(), Equals, test.Life)
1515
c.Assert(s.mysql.IsExposed(), Equals, test.Exposed)
1516
case <-time.After(500 * time.Millisecond):
1517
c.Fatalf("did not get change: %v %v", test.Exposed, test.Life)
1522
case got, ok := <-w.Changes():
1523
c.Fatalf("got unexpected change: %#v, %v", got, ok)
1524
case <-time.After(100 * time.Millisecond):
1528
func (s *ServiceSuite) TestWatchServiceConfig(c *C) {
1529
config, err := s.mysql.Config()
1530
c.Assert(err, IsNil)
1531
c.Assert(config.Keys(), HasLen, 0)
1533
u, err := s.mysql.AddUnit()
1534
c.Assert(err, IsNil)
1536
_, err = u.WatchServiceConfig()
1537
c.Assert(err, ErrorMatches, "unit charm not set")
1539
err = u.SetCharmURL(s.charm.URL())
1540
c.Assert(err, IsNil)
1542
configWatcher, err := u.WatchServiceConfig()
1543
c.Assert(err, IsNil)
1545
c.Assert(configWatcher.Stop(), IsNil)
1550
case got, ok := <-configWatcher.Changes():
1551
c.Assert(ok, Equals, true)
1552
c.Assert(got.Map(), DeepEquals, map[string]interface{}{})
1553
case <-time.After(500 * time.Millisecond):
1554
c.Fatalf("did not get change")
1557
// Two change events.
1558
config.Set("foo", "bar")
1559
config.Set("baz", "yadda")
1560
changes, err := config.Write()
1561
c.Assert(err, IsNil)
1562
c.Assert(changes, DeepEquals, []state.ItemChange{{
1564
Type: state.ItemAdded,
1568
Type: state.ItemAdded,
1574
case got, ok := <-configWatcher.Changes():
1575
c.Assert(ok, Equals, true)
1576
c.Assert(got.Map(), DeepEquals, map[string]interface{}{"baz": "yadda", "foo": "bar"})
1577
case <-time.After(500 * time.Millisecond):
1578
c.Fatalf("did not get change")
1581
config.Delete("foo")
1582
changes, err = config.Write()
1583
c.Assert(err, IsNil)
1584
c.Assert(changes, DeepEquals, []state.ItemChange{{
1586
Type: state.ItemDeleted,
1592
case got, ok := <-configWatcher.Changes():
1593
c.Assert(ok, Equals, true)
1594
c.Assert(got.Map(), DeepEquals, map[string]interface{}{"baz": "yadda"})
1595
case <-time.After(500 * time.Millisecond):
1596
c.Fatalf("did not get change")
1600
case got := <-configWatcher.Changes():
1601
c.Fatalf("got unexpected change: %#v", got)
1602
case <-time.After(100 * time.Millisecond):
1606
func (s *ServiceSuite) TestAnnotatorForService(c *C) {
1607
testAnnotator(c, func() (state.Annotator, error) {
1608
return s.State.Service("mysql")
1612
func (s *ServiceSuite) TestAnnotationRemovalForService(c *C) {
1613
annotations := map[string]string{"mykey": "myvalue"}
1614
err := s.mysql.SetAnnotations(annotations)
1615
c.Assert(err, IsNil)
1616
err = s.mysql.Destroy()
1617
c.Assert(err, IsNil)
1618
ann, err := s.mysql.Annotations()
1619
c.Assert(err, IsNil)
1620
c.Assert(ann, DeepEquals, make(map[string]string))