22
22
from juju.state.relation import RelationStateManager
23
23
from juju.state.utils import YAMLState
24
24
from juju.state.errors import (
25
StateChanged, ServiceStateNotFound, ServiceUnitStateNotFound,
26
ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse,
27
BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled,
28
MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled,
29
ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher,
30
ServiceUnitUpgradeAlreadyEnabled)
25
StateChanged, ServiceStateNotFound, ServiceUnitStateNotFound,
26
ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse,
27
BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled,
28
MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled,
29
ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher,
30
ServiceUnitUpgradeAlreadyEnabled)
32
31
from juju.state.tests.common import StateTestBase
35
34
class ServiceStateManagerTestBase(StateTestBase):
39
yield super(ServiceStateManagerTestBase, self).setUp()
40
yield self.push_default_config()
41
self.charm_state_manager = CharmStateManager(self.client)
42
self.service_state_manager = ServiceStateManager(self.client)
43
self.machine_state_manager = MachineStateManager(self.client)
44
self.charm_state = yield self.charm_state_manager.add_charm_state(
45
local_charm_id(self.charm), self.charm, "")
46
self.relation_state_manager = RelationStateManager(self.client)
49
def add_service(self, name):
50
service_state = yield self.service_state_manager.add_service_state(
51
name, self.charm_state, dummy_constraints)
52
returnValue(service_state)
55
def add_service_from_charm(
56
self, service_name, charm_id=None, constraints=None,
57
charm_dir=None, charm_name=None):
58
"""Add a service from a charm.
60
if not charm_id and charm_dir is None:
61
charm_name = charm_name or service_name
62
charm_dir = CharmDirectory(os.path.join(
63
test_repository_path, "series", charm_name))
65
charm_state = yield self.charm_state_manager.add_charm_state(
66
local_charm_id(charm_dir), charm_dir, "")
68
charm_state = yield self.charm_state_manager.get_charm_state(
70
service_state = yield self.service_state_manager.add_service_state(
71
service_name, charm_state, constraints or dummy_constraints)
72
returnValue(service_state)
75
def get_subordinate_charm(self):
76
"""Return charm state for a subordinate charm.
78
Many tests rely on adding relationships to a proper subordinate.
79
This return the charm state of a testing subordinate charm.
81
unbundled_repo_path = self.makeDir()
82
os.rmdir(unbundled_repo_path)
83
shutil.copytree(unbundled_repository, unbundled_repo_path)
85
sub_charm = CharmDirectory(
86
os.path.join(unbundled_repo_path, "series", "logging"))
87
self.charm_state_manager.add_charm_state("local:series/logging-1",
89
logging_charm_state = yield self.charm_state_manager.get_charm_state(
90
"local:series/logging-1")
91
returnValue(logging_charm_state)
94
def add_relation(self, relation_type, relation_scope, *services):
96
for service_meta in services:
97
service_state, relation_name, relation_role = service_meta
98
endpoints.append(RelationEndpoint(
99
service_state.service_name,
104
relation_state = yield self.relation_state_manager.add_relation_state(
106
returnValue(relation_state[0])
109
def remove_service(self, internal_service_id):
110
topology = yield self.get_topology()
111
topology.remove_service(internal_service_id)
112
yield self.set_topology(topology)
115
def remove_service_unit(self, internal_service_id, internal_unit_id):
116
topology = yield self.get_topology()
117
topology.remove_service_unit(internal_service_id, internal_unit_id)
118
yield self.set_topology(topology)
120
def add_machine_state(self, constraints=None):
121
return self.machine_state_manager.add_machine_state(
122
constraints or series_constraints)
125
def assert_machine_states(self, present, absent):
126
"""Assert that machine IDs are either `present` or `absent` in topo."""
127
for machine_id in present:
129
(yield self.machine_state_manager.get_machine_state(
131
for machine_id in absent:
132
ex = yield self.assertFailure(
133
self.machine_state_manager.get_machine_state(machine_id),
134
MachineStateNotFound)
135
self.assertEqual(ex.machine_id, machine_id)
138
def assert_machine_assignments(self, service_name, machine_ids):
139
"""Assert that `service_name` is deployed on `machine_ids`."""
140
topology = yield self.get_topology()
141
service_id = topology.find_service_with_name(service_name)
142
assigned_machine_ids = [
143
topology.get_service_unit_machine(service_id, unit_id)
144
for unit_id in topology.get_service_units(service_id)]
145
internal_machine_ids = []
146
for machine_id in machine_ids:
147
if machine_id is None:
148
# corresponds to get_service_unit_machine API in this case
149
internal_machine_ids.append(None)
151
internal_machine_ids.append("machine-%010d" % machine_id)
153
set(assigned_machine_ids), set(internal_machine_ids))
156
def get_unit_state(self):
157
"""Simple test helper to get a unit state."""
158
service_state = yield self.service_state_manager.add_service_state(
159
"wordpress", self.charm_state, dummy_constraints)
160
unit_state = yield service_state.add_unit_state()
161
returnValue(unit_state)
38
yield super(ServiceStateManagerTestBase, self).setUp()
39
yield self.push_default_config()
40
self.charm_state_manager = CharmStateManager(self.client)
41
self.service_state_manager = ServiceStateManager(self.client)
42
self.machine_state_manager = MachineStateManager(self.client)
43
self.charm_state = yield self.charm_state_manager.add_charm_state(
44
local_charm_id(self.charm), self.charm, "")
45
self.relation_state_manager = RelationStateManager(self.client)
48
def add_service(self, name):
49
service_state = yield self.service_state_manager.add_service_state(
50
name, self.charm_state, dummy_constraints)
51
returnValue(service_state)
54
def add_service_from_charm(
55
self, service_name, charm_id=None, constraints=None,
56
charm_dir=None, charm_name=None):
57
"""Add a service from a charm.
59
if not charm_id and charm_dir is None:
60
charm_name = charm_name or service_name
61
charm_dir = CharmDirectory(os.path.join(
62
test_repository_path, "series", charm_name))
64
charm_state = yield self.charm_state_manager.add_charm_state(
65
local_charm_id(charm_dir), charm_dir, "")
67
charm_state = yield self.charm_state_manager.get_charm_state(
69
service_state = yield self.service_state_manager.add_service_state(
70
service_name, charm_state, constraints or dummy_constraints)
71
returnValue(service_state)
74
def get_subordinate_charm(self):
75
"""Return charm state for a subordinate charm.
77
Many tests rely on adding relationships to a proper subordinate.
78
This return the charm state of a testing subordinate charm.
80
unbundled_repo_path = self.makeDir()
81
os.rmdir(unbundled_repo_path)
82
shutil.copytree(unbundled_repository, unbundled_repo_path)
84
sub_charm = CharmDirectory(
85
os.path.join(unbundled_repo_path, "series", "logging"))
86
self.charm_state_manager.add_charm_state("local:series/logging-1",
88
logging_charm_state = yield self.charm_state_manager.get_charm_state(
89
"local:series/logging-1")
90
returnValue(logging_charm_state)
93
def add_relation(self, relation_type, relation_scope, *services):
95
for service_meta in services:
96
service_state, relation_name, relation_role = service_meta
97
endpoints.append(RelationEndpoint(
98
service_state.service_name,
103
relation_state = yield self.relation_state_manager.add_relation_state(
105
returnValue(relation_state[0])
108
def remove_service(self, internal_service_id):
109
topology = yield self.get_topology()
110
topology.remove_service(internal_service_id)
111
yield self.set_topology(topology)
114
def remove_service_unit(self, internal_service_id, internal_unit_id):
115
topology = yield self.get_topology()
116
topology.remove_service_unit(internal_service_id, internal_unit_id)
117
yield self.set_topology(topology)
119
def add_machine_state(self, constraints=None):
120
return self.machine_state_manager.add_machine_state(
121
constraints or series_constraints)
124
def assert_machine_states(self, present, absent):
125
"""Assert that machine IDs are either `present` or `absent` in topo."""
126
for machine_id in present:
128
(yield self.machine_state_manager.get_machine_state(
130
for machine_id in absent:
131
ex = yield self.assertFailure(
132
self.machine_state_manager.get_machine_state(machine_id),
133
MachineStateNotFound)
134
self.assertEqual(ex.machine_id, machine_id)
137
def assert_machine_assignments(self, service_name, machine_ids):
138
"""Assert that `service_name` is deployed on `machine_ids`."""
139
topology = yield self.get_topology()
140
service_id = topology.find_service_with_name(service_name)
141
assigned_machine_ids = [
142
topology.get_service_unit_machine(service_id, unit_id)
143
for unit_id in topology.get_service_units(service_id)]
144
internal_machine_ids = []
145
for machine_id in machine_ids:
146
if machine_id is None:
147
# corresponds to get_service_unit_machine API in this case
148
internal_machine_ids.append(None)
150
internal_machine_ids.append("machine-%010d" % machine_id)
152
set(assigned_machine_ids), set(internal_machine_ids))
155
def get_unit_state(self):
156
"""Simple test helper to get a unit state."""
157
service_state = yield self.service_state_manager.add_service_state(
158
"wordpress", self.charm_state, dummy_constraints)
159
unit_state = yield service_state.add_unit_state()
160
returnValue(unit_state)
164
163
class ServiceStateManagerTest(ServiceStateManagerTestBase):
167
def test_add_service(self):
169
Adding a service state should register it in zookeeper,
170
including the requested charm id.
172
yield self.service_state_manager.add_service_state(
173
"wordpress", self.charm_state, dummy_constraints)
174
yield self.service_state_manager.add_service_state(
175
"mysql", self.charm_state, dummy_constraints)
176
children = yield self.client.get_children("/services")
178
self.assertEquals(sorted(children),
179
["service-0000000000", "service-0000000001"])
181
content, stat = yield self.client.get("/services/service-0000000000")
182
details = yaml.load(content)
183
self.assertTrue(details)
184
self.assertEquals(details.get("charm"), "local:series/dummy-1")
185
self.assertFalse(isinstance(details.get("charm"), unicode))
186
self.assertEquals(details.get("constraints"), series_constraints.data)
188
topology = yield self.get_topology()
189
self.assertEquals(topology.find_service_with_name("wordpress"),
190
"service-0000000000")
191
self.assertEquals(topology.find_service_with_name("mysql"),
192
"service-0000000001")
195
def test_add_service_with_duplicated_name(self):
197
If a service is added with a duplicated name, a meaningful
198
error should be raised.
200
yield self.service_state_manager.add_service_state(
201
"wordpress", self.charm_state, dummy_constraints)
204
yield self.service_state_manager.add_service_state(
205
"wordpress", self.charm_state, dummy_constraints)
206
except ServiceStateNameInUse, e:
207
self.assertEquals(e.service_name, "wordpress")
209
self.fail("Error not raised")
212
def test_get_service_and_check_attributes(self):
214
Getting a service state should be possible, and the service
215
state identification should be available through its
218
yield self.service_state_manager.add_service_state(
219
"wordpress", self.charm_state, dummy_constraints)
220
service_state = yield self.service_state_manager.get_service_state(
222
self.assertEquals(service_state.service_name, "wordpress")
223
self.assertEquals(service_state.internal_id, "service-0000000000")
226
def test_get_service_not_found(self):
228
Getting a service state which is not available should errback
232
yield self.service_state_manager.get_service_state("wordpress")
233
except ServiceStateNotFound, e:
234
self.assertEquals(e.service_name, "wordpress")
236
self.fail("Error not raised")
238
def test_get_unit_state(self):
239
"""A unit state can be retrieved by name from the service manager."""
240
self.assertFailure(self.service_state_manager.get_unit_state(
241
"wordpress/1"), ServiceStateNotFound)
243
self.assertFailure(self.service_state_manager.get_unit_state(
244
"wordpress1"), ServiceUnitStateNotFound)
246
wordpress_state = yield self.service_state_manager.add_service_state(
247
"wordpress", self.charm_state, dummy_constraints)
249
self.assertFailure(self.service_state_manager.get_unit_state(
250
"wordpress/1"), ServiceUnitStateNotFound)
252
wordpress_unit = wordpress_state.add_unit_state()
254
unit_state = yield self.service_state_manager.get_unit_state(
257
self.assertEqual(unit_state.internal_id, wordpress_unit.internal_id)
260
def test_get_service_charm_id(self):
262
The service state should make its respective charm id available.
264
yield self.service_state_manager.add_service_state(
265
"wordpress", self.charm_state, dummy_constraints)
266
service_state = yield self.service_state_manager.get_service_state(
268
charm_id = yield service_state.get_charm_id()
269
self.assertEquals(charm_id, "local:series/dummy-1")
272
def test_set_service_charm_id(self):
274
The service state should allow its charm id to be set.
276
yield self.service_state_manager.add_service_state(
277
"wordpress", self.charm_state, dummy_constraints)
278
service_state = yield self.service_state_manager.get_service_state(
280
yield service_state.set_charm_id("local:series/dummy-2")
281
charm_id = yield service_state.get_charm_id()
282
self.assertEquals(charm_id, "local:series/dummy-2")
285
def test_get_service_constraints(self):
286
"""The service state should make constraints available"""
287
initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"])
288
yield self.service_state_manager.add_service_state(
289
"wordpress", self.charm_state, initial_constraints)
290
service_state = yield self.service_state_manager.get_service_state(
292
constraints = yield service_state.get_constraints()
294
constraints, initial_constraints.with_series("series"))
297
def test_get_service_constraints_inherits(self):
298
"""The service constraints should be combined with the environment's"""
299
yield self.push_env_constraints("arch=arm", "cpu=32")
300
service_constraints = dummy_cs.parse(["cpu=256"])
301
yield self.service_state_manager.add_service_state(
302
"wordpress", self.charm_state, service_constraints)
303
service_state = yield self.service_state_manager.get_service_state(
305
constraints = yield service_state.get_constraints()
306
expected_base = dummy_cs.parse(["arch=arm", "cpu=256"])
307
self.assertEquals(constraints, expected_base.with_series("series"))
310
def test_get_missing_service_constraints(self):
312
Nodes created before the constraints mechanism was added should have
315
yield self.client.delete("/constraints")
316
yield self.service_state_manager.add_service_state(
317
"wordpress", self.charm_state, dummy_constraints)
318
service_state = yield self.service_state_manager.get_service_state(
320
path = "/services/" + service_state.internal_id
321
node = YAMLState(self.client, path)
323
del node["constraints"]
325
constraints = yield service_state.get_constraints()
326
self.assertEquals(constraints.data, {})
329
def test_get_missing_unit_constraints(self):
331
Nodes created before the constraints mechanism was added should have
334
yield self.service_state_manager.add_service_state(
335
"wordpress", self.charm_state, dummy_constraints)
336
service_state = yield self.service_state_manager.get_service_state(
338
unit_state = yield service_state.add_unit_state()
339
path = "/units/" + unit_state.internal_id
340
node = YAMLState(self.client, path)
342
del node["constraints"]
344
constraints = yield unit_state.get_constraints()
345
self.assertEquals(constraints.data, {})
348
def test_set_service_constraints(self):
349
"""The service state should make constraints available for change"""
350
initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"])
351
yield self.service_state_manager.add_service_state(
352
"wordpress", self.charm_state, initial_constraints)
353
service_state = yield self.service_state_manager.get_service_state(
355
new_constraints = dummy_cs.parse(["mem=2G", "arch=arm"])
356
yield service_state.set_constraints(new_constraints)
357
retrieved_constraints = yield service_state.get_constraints()
359
retrieved_constraints, new_constraints.with_series("series"))
362
def test_remove_service_state(self):
364
A service state can be removed along with its relations, units,
367
service_state = yield self.service_state_manager.add_service_state(
368
"wordpress", self.charm_state, dummy_constraints)
370
relation_state = yield self.add_relation(
371
"rel-type2", "global", [service_state, "app", "server"])
373
unit_state = yield service_state.add_unit_state()
374
machine_state = yield self.machine_state_manager.add_machine_state(
376
yield unit_state.assign_to_machine(machine_state)
378
yield self.service_state_manager.remove_service_state(service_state)
380
topology = yield self.get_topology()
381
self.assertFalse(topology.has_relation(relation_state.internal_id))
382
self.assertFalse(topology.has_service(service_state.internal_id))
384
topology.get_service_units_in_machine(machine_state.internal_id))
386
exists = yield self.client.exists(
387
"/services/%s" % service_state.internal_id)
388
self.assertFalse(exists)
391
def test_add_service_unit_and_check_attributes(self):
393
A service state should enable adding a new service unit
394
under it, and again the unit should offer attributes allowing
397
service_state0 = yield self.service_state_manager.add_service_state(
398
"wordpress", self.charm_state, dummy_constraints)
399
service_state1 = yield self.service_state_manager.add_service_state(
400
"mysql", self.charm_state, dummy_constraints)
402
unit_state0 = yield service_state0.add_unit_state()
403
unit_state1 = yield service_state1.add_unit_state()
404
unit_state2 = yield service_state0.add_unit_state()
405
unit_state3 = yield service_state1.add_unit_state()
407
children = yield self.client.get_children("/units")
408
self.assertEquals(sorted(children),
409
["unit-0000000000", "unit-0000000001",
410
"unit-0000000002", "unit-0000000003"])
412
self.assertEquals(unit_state0.service_name, "wordpress")
413
self.assertEquals(unit_state0.internal_id, "unit-0000000000")
414
self.assertEquals(unit_state0.unit_name, "wordpress/0")
416
self.assertEquals(unit_state1.service_name, "mysql")
417
self.assertEquals(unit_state1.internal_id, "unit-0000000001")
418
self.assertEquals(unit_state1.unit_name, "mysql/0")
420
self.assertEquals(unit_state2.service_name, "wordpress")
421
self.assertEquals(unit_state2.internal_id, "unit-0000000002")
422
self.assertEquals(unit_state2.unit_name, "wordpress/1")
424
self.assertEquals(unit_state3.service_name, "mysql")
425
self.assertEquals(unit_state3.internal_id, "unit-0000000003")
426
self.assertEquals(unit_state3.unit_name, "mysql/1")
428
topology = yield self.get_topology()
431
topology.has_service_unit("service-0000000000",
434
topology.has_service_unit("service-0000000001",
437
topology.has_service_unit("service-0000000000",
440
topology.has_service_unit("service-0000000001",
443
def get_presence_path(
444
self, relation_state, relation_role, unit_state, container=None):
445
container = container.internal_id if container else None
446
presence_path = "/".join(filter(None, [
448
relation_state.internal_id,
451
unit_state.internal_id]))
455
def test_add_service_unit_with_container(self):
457
Validate adding units with containers specified and recovering that.
459
mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info",
461
logging_ep = RelationEndpoint("logging", "juju-info", "juju-info",
462
"client", "container")
464
logging_charm_state = yield self.get_subordinate_charm()
465
self.assertTrue(logging_charm_state.is_subordinate())
466
log_state = yield self.service_state_manager.add_service_state(
467
"logging", logging_charm_state, dummy_constraints)
468
mysql_state = yield self.service_state_manager.add_service_state(
469
"mysql", self.charm_state, dummy_constraints)
471
relation_state, service_states = (yield
472
self.relation_state_manager.add_relation_state(
473
mysql_ep, logging_ep))
475
unit_state1 = yield mysql_state.add_unit_state()
476
unit_state0 = yield log_state.add_unit_state(container=unit_state1)
478
unit_state3 = yield mysql_state.add_unit_state()
479
unit_state2 = yield log_state.add_unit_state(container=unit_state3)
481
self.assertEquals((yield unit_state1.get_container()), None)
482
self.assertEquals((yield unit_state0.get_container()), unit_state1)
483
self.assertEquals((yield unit_state2.get_container()), unit_state3)
484
self.assertEquals((yield unit_state3.get_container()), None)
486
for unit_state in (unit_state1, unit_state3):
487
yield unit_state.set_private_address(
489
unit_state.unit_name.replace("/", "-")))
491
# construct the proper relation state
492
mystate = pick_attr(service_states, relation_role="server")
493
logstate = pick_attr(service_states, relation_role="client")
494
yield logstate.add_unit_state(unit_state0)
495
yield logstate.add_unit_state(unit_state2)
497
yield mystate.add_unit_state(unit_state1)
498
yield mystate.add_unit_state(unit_state3)
501
def verify_container(relation_state, service_relation_state,
502
unit_state, container):
503
presence_path = self.get_presence_path(
505
service_relation_state.relation_role,
509
content, stat = yield self.client.get(presence_path)
510
self.assertTrue(stat)
511
self.assertEqual(content, '')
512
# verify the node data on the relation role nodes
513
role_path = os.path.dirname(presence_path)
515
content, stat = yield self.client.get(role_path)
516
self.assertTrue(stat)
517
node_info = yaml.load(content)
521
service_relation_state.relation_name)
524
service_relation_state.relation_role)
526
settings_path = os.path.dirname(
527
os.path.dirname(presence_path)) + "/settings/" + \
528
unit_state.internal_id
529
content, stat = yield self.client.get(settings_path)
530
self.assertTrue(stat)
531
settings_info = yaml.load(content)
533
# Verify that private address was set
534
# we verify the content elsewhere
535
self.assertTrue(settings_info["private-address"])
537
# verify all the units are constructed as expected
538
# first the client roles with another container
539
yield verify_container(relation_state, logstate,
540
unit_state0, unit_state1)
542
yield verify_container(relation_state, logstate,
543
unit_state2, unit_state3)
545
# and now the principals (which are their own relation containers)
546
yield verify_container(relation_state, logstate,
547
unit_state0, unit_state1)
549
yield verify_container(relation_state, mystate,
550
unit_state1, unit_state1)
553
def test_get_container_no_principal(self):
554
"""Get container should handle no principal."""
555
mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info",
557
logging_ep = RelationEndpoint("logging", "juju-info", "juju-info",
558
"client", "container")
560
logging_charm_state = yield self.get_subordinate_charm()
561
self.assertTrue(logging_charm_state.is_subordinate())
562
log_state = yield self.service_state_manager.add_service_state(
563
"logging", logging_charm_state, dummy_constraints)
564
mysql_state = yield self.service_state_manager.add_service_state(
565
"mysql", self.charm_state, dummy_constraints)
567
relation_state, service_states = (yield
568
self.relation_state_manager.add_relation_state(
569
mysql_ep, logging_ep))
571
unit_state1 = yield mysql_state.add_unit_state()
572
unit_state0 = yield log_state.add_unit_state(container=unit_state1)
574
unit_state3 = yield mysql_state.add_unit_state()
575
unit_state2 = yield log_state.add_unit_state(container=unit_state3)
577
self.assertEquals((yield unit_state1.get_container()), None)
578
self.assertEquals((yield unit_state0.get_container()), unit_state1)
579
self.assertEquals((yield unit_state2.get_container()), unit_state3)
580
self.assertEquals((yield unit_state3.get_container()), None)
582
# now remove a principal node and test again
584
yield mysql_state.remove_unit_state(unit_state1)
585
container = yield unit_state0.get_container()
586
self.assertEquals(container, None)
588
# the other pair is still fine
589
self.assertEquals((yield unit_state2.get_container()), unit_state3)
590
self.assertEquals((yield unit_state3.get_container()), None)
594
def test_add_service_unit_with_changing_state(self):
596
When adding a service unit, there's a chance that the
597
service will go away mid-way through. Rather than blowing
598
up randomly, a nice error should be raised.
600
service_state = yield self.service_state_manager.add_service_state(
601
"wordpress", self.charm_state, dummy_constraints)
603
yield self.remove_service(service_state.internal_id)
605
d = service_state.add_unit_state()
606
yield self.assertFailure(d, StateChanged)
609
def test_get_unit_names(self):
610
"""A service's units names are retrievable."""
611
service_state = yield self.service_state_manager.add_service_state(
612
"wordpress", self.charm_state, dummy_constraints)
616
unit_state = yield service_state.add_unit_state()
617
expected_names.append(unit_state.unit_name)
619
unit_names = yield service_state.get_unit_names()
620
self.assertEqual(unit_names, expected_names)
623
def test_remove_service_unit(self):
624
"""Removing a service unit removes all state associated.
626
service_state = yield self.service_state_manager.add_service_state(
627
"wordpress", self.charm_state, dummy_constraints)
629
unit_state = yield service_state.add_unit_state()
631
# Assign to a machine
632
machine_state = yield self.machine_state_manager.add_machine_state(
634
yield unit_state.assign_to_machine(machine_state)
635
# Connect a unit agent
636
yield unit_state.connect_agent()
638
# Now try and destroy it.
639
yield service_state.remove_unit_state(unit_state)
641
# Verify destruction.
642
topology = yield self.get_topology()
643
self.assertTrue(topology.has_service(service_state.internal_id))
645
topology.has_service_unit(
646
service_state.internal_id, unit_state.internal_id))
648
exists = yield self.client.exists("/units/%s" % unit_state.internal_id)
649
self.assertFalse(exists)
651
def test_remove_service_unit_nonexistant(self):
652
"""Removing a non existant service unit, is fine."""
654
service_state = yield self.service_state_manager.add_service_state(
655
"wordpress", self.charm_state, dummy_constraints)
656
unit_state = yield service_state.add_unit_state()
657
yield service_state.remove_unit_state(unit_state)
658
yield service_state.remove_unit_state(unit_state)
661
def test_get_all_service_states(self):
662
services = yield self.service_state_manager.get_all_service_states()
663
self.assertFalse(services)
665
yield self.service_state_manager.add_service_state(
666
"wordpress", self.charm_state, dummy_constraints)
667
services = yield self.service_state_manager.get_all_service_states()
668
self.assertEquals(len(services), 1)
670
yield self.service_state_manager.add_service_state(
671
"mysql", self.charm_state, dummy_constraints)
672
services = yield self.service_state_manager.get_all_service_states()
673
self.assertEquals(len(services), 2)
676
def test_get_service_unit(self):
678
Getting back service units should be possible using the
681
service_state0 = yield self.service_state_manager.add_service_state(
682
"wordpress", self.charm_state, dummy_constraints)
683
service_state1 = yield self.service_state_manager.add_service_state(
684
"mysql", self.charm_state, dummy_constraints)
686
yield service_state0.add_unit_state()
687
yield service_state1.add_unit_state()
688
yield service_state0.add_unit_state()
689
yield service_state1.add_unit_state()
691
unit_state0 = yield service_state0.get_unit_state("wordpress/0")
692
unit_state1 = yield service_state1.get_unit_state("mysql/0")
693
unit_state2 = yield service_state0.get_unit_state("wordpress/1")
694
unit_state3 = yield service_state1.get_unit_state("mysql/1")
696
self.assertEquals(unit_state0.internal_id, "unit-0000000000")
697
self.assertEquals(unit_state1.internal_id, "unit-0000000001")
698
self.assertEquals(unit_state2.internal_id, "unit-0000000002")
699
self.assertEquals(unit_state3.internal_id, "unit-0000000003")
701
self.assertEquals(unit_state0.unit_name, "wordpress/0")
702
self.assertEquals(unit_state1.unit_name, "mysql/0")
703
self.assertEquals(unit_state2.unit_name, "wordpress/1")
704
self.assertEquals(unit_state3.unit_name, "mysql/1")
707
def test_get_all_unit_states(self):
708
service_state0 = yield self.service_state_manager.add_service_state(
709
"wordpress", self.charm_state, dummy_constraints)
710
service_state1 = yield self.service_state_manager.add_service_state(
711
"mysql", self.charm_state, dummy_constraints)
713
yield service_state0.add_unit_state()
714
yield service_state1.add_unit_state()
715
yield service_state0.add_unit_state()
716
yield service_state1.add_unit_state()
718
unit_state0 = yield service_state0.get_unit_state("wordpress/0")
719
unit_state1 = yield service_state1.get_unit_state("mysql/0")
720
unit_state2 = yield service_state0.get_unit_state("wordpress/1")
721
unit_state3 = yield service_state1.get_unit_state("mysql/1")
723
wordpress_units = yield service_state0.get_all_unit_states()
725
set(wordpress_units), set((unit_state0, unit_state2)))
727
mysql_units = yield service_state1.get_all_unit_states()
728
self.assertEquals(set(mysql_units), set((unit_state1, unit_state3)))
731
def test_get_all_unit_states_with_changing_state(self):
733
When getting the service unit states, there's a chance that
734
the service will go away mid-way through. Rather than blowing
735
up randomly, a nice error should be raised.
737
service_state = yield self.service_state_manager.add_service_state(
738
"wordpress", self.charm_state, dummy_constraints)
739
yield service_state.add_unit_state()
740
unit_state = (yield service_state.get_all_unit_states())[0]
741
self.assertEqual(unit_state.unit_name, "wordpress/0")
742
yield self.remove_service(service_state.internal_id)
743
yield self.assertFailure(
744
service_state.get_all_unit_states(), StateChanged)
747
def test_set_functions(self):
748
wordpress = yield self.service_state_manager.add_service_state(
749
"wordpress", self.charm_state, dummy_constraints)
750
mysql = yield self.service_state_manager.add_service_state(
751
"mysql", self.charm_state, dummy_constraints)
753
s1 = yield self.service_state_manager.get_service_state(
755
s2 = yield self.service_state_manager.get_service_state(
757
self.assertEquals(hash(s1), hash(wordpress))
758
self.assertEquals(hash(s2), hash(mysql))
760
self.assertNotEqual(s1, object())
761
self.assertNotEqual(s1, s2)
763
self.assertEquals(s1, wordpress)
764
self.assertEquals(s2, mysql)
766
us0 = yield wordpress.add_unit_state()
767
us1 = yield wordpress.add_unit_state()
769
unit_state0 = yield wordpress.get_unit_state("wordpress/0")
770
unit_state1 = yield wordpress.get_unit_state("wordpress/1")
772
self.assertEquals(us0, unit_state0)
773
self.assertEquals(us1, unit_state1)
774
self.assertEquals(hash(us1), hash(unit_state1))
776
self.assertNotEqual(us0, object())
777
self.assertNotEqual(us0, us1)
780
def test_get_service_unit_not_found(self):
782
Attempting to retrieve a non-existent service unit should
783
result in an errback.
785
service_state0 = yield self.service_state_manager.add_service_state(
786
"wordpress", self.charm_state, dummy_constraints)
787
service_state1 = yield self.service_state_manager.add_service_state(
788
"mysql", self.charm_state, dummy_constraints)
790
# Add some state in a different service to make it a little
791
# bit more prone to breaking in case of errors.
792
yield service_state1.add_unit_state()
795
yield service_state0.get_unit_state("wordpress/0")
796
except ServiceUnitStateNotFound, e:
797
self.assertEquals(e.unit_name, "wordpress/0")
799
self.fail("Error not raised")
802
def test_get_set_public_address(self):
803
service_state = yield self.service_state_manager.add_service_state(
804
"wordpress", self.charm_state, dummy_constraints)
805
unit_state = yield service_state.add_unit_state()
806
self.assertEqual((yield unit_state.get_public_address()), None)
807
yield unit_state.set_public_address("example.foobar.com")
808
yield self.assertEqual(
809
(yield unit_state.get_public_address()),
810
"example.foobar.com")
813
def test_get_set_private_address(self):
814
service_state = yield self.service_state_manager.add_service_state(
815
"wordpress", self.charm_state, dummy_constraints)
816
unit_state = yield service_state.add_unit_state()
817
self.assertEqual((yield unit_state.get_private_address()), None)
818
yield unit_state.set_private_address("example.local")
819
yield self.assertEqual(
820
(yield unit_state.get_private_address()),
824
def test_get_service_unit_with_changing_state(self):
826
If a service is removed during operation, get_service_unit()
827
should raise a nice error.
829
service_state = yield self.service_state_manager.add_service_state(
830
"wordpress", self.charm_state, dummy_constraints)
832
yield self.remove_service(service_state.internal_id)
834
d = service_state.get_unit_state("wordpress/0")
835
yield self.assertFailure(d, StateChanged)
838
def test_get_service_unit_with_bad_service_name(self):
840
Service unit names contain a service name embedded into
841
them. The service name requested when calling get_unit_state()
842
must match that of the object being used.
844
service_state0 = yield self.service_state_manager.add_service_state(
845
"wordpress", self.charm_state, dummy_constraints)
846
service_state1 = yield self.service_state_manager.add_service_state(
847
"mysql", self.charm_state, dummy_constraints)
849
# Add some state in a different service to make it a little
850
# bit more prone to breaking in case of errors.
851
yield service_state1.add_unit_state()
854
yield service_state0.get_unit_state("mysql/0")
855
except BadServiceStateName, e:
856
self.assertEquals(e.expected_name, "wordpress")
857
self.assertEquals(e.obtained_name, "mysql")
859
self.fail("Error not raised")
862
def test_assign_unit_to_machine(self):
863
service_state = yield self.service_state_manager.add_service_state(
864
"wordpress", self.charm_state, dummy_constraints)
865
unit_state = yield service_state.add_unit_state()
866
machine_state = yield self.machine_state_manager.add_machine_state(
869
yield unit_state.assign_to_machine(machine_state)
871
topology = yield self.get_topology()
874
topology.get_service_unit_machine(service_state.internal_id,
875
unit_state.internal_id),
876
machine_state.internal_id)
879
def test_assign_unit_to_machine_with_changing_state(self):
880
service_state = yield self.service_state_manager.add_service_state(
881
"wordpress", self.charm_state, dummy_constraints)
882
unit_state = yield service_state.add_unit_state()
883
machine_state = yield self.machine_state_manager.add_machine_state(
886
yield self.remove_service_unit(service_state.internal_id,
887
unit_state.internal_id)
889
d = unit_state.assign_to_machine(machine_state)
890
yield self.assertFailure(d, StateChanged)
892
yield self.remove_service(service_state.internal_id)
894
d = unit_state.assign_to_machine(machine_state)
895
yield self.assertFailure(d, StateChanged)
898
def test_unassign_unit_from_machine(self):
899
service_state = yield self.service_state_manager.add_service_state(
900
"wordpress", self.charm_state, dummy_constraints)
901
unit_state = yield service_state.add_unit_state()
902
machine_state = yield self.machine_state_manager.add_machine_state(
905
yield unit_state.assign_to_machine(machine_state)
906
yield unit_state.unassign_from_machine()
908
topology = yield self.get_topology()
911
topology.get_service_unit_machine(service_state.internal_id,
912
unit_state.internal_id),
916
def test_get_set_clear_resolved(self):
917
"""The a unit can be set to resolved to mark a future transition, with
918
an optional retry flag."""
920
unit_state = yield self.get_unit_state()
922
self.assertIdentical((yield unit_state.get_resolved()), None)
923
yield unit_state.set_resolved(NO_HOOKS)
925
yield self.assertFailure(
926
unit_state.set_resolved(NO_HOOKS),
927
ServiceUnitResolvedAlreadyEnabled)
928
yield self.assertEqual(
930
(yield unit_state.get_resolved()), {"retry": NO_HOOKS})
932
yield unit_state.clear_resolved()
933
self.assertIdentical((yield unit_state.get_resolved()), None)
934
yield unit_state.clear_resolved()
936
yield self.assertFailure(unit_state.set_resolved(None), ValueError)
939
def test_watch_resolved(self):
940
"""A unit resolved watch can be instituted on a permanent basis."""
941
unit_state = yield self.get_unit_state()
946
results.append(value)
948
unit_state.watch_resolved(callback)
949
yield unit_state.set_resolved(RETRY_HOOKS)
950
yield unit_state.clear_resolved()
951
yield unit_state.set_resolved(NO_HOOKS)
955
self.assertEqual(len(results), 4)
956
self.assertIdentical(results.pop(0), False)
957
self.assertEqual(results.pop(0).type_name, "created")
958
self.assertEqual(results.pop(0).type_name, "deleted")
959
self.assertEqual(results.pop(0).type_name, "created")
962
(yield unit_state.get_resolved()),
966
def test_watch_resolved_processes_current_state(self):
967
"""The watch method processes the current state before returning."""
968
unit_state = yield self.get_unit_state()
974
results.append((yield unit_state.get_resolved()))
976
yield unit_state.watch_resolved(callback)
977
self.assertTrue(results)
980
def test_stop_watch_resolved(self):
981
"""A unit resolved watch can be instituted on a permanent basis.
983
However the callback can raise StopWatcher at anytime to stop the watch
985
unit_state = yield self.get_unit_state()
990
results.append(value)
991
if len(results) == 1:
993
if len(results) == 3:
996
unit_state.watch_resolved(callback)
997
yield unit_state.set_resolved(RETRY_HOOKS)
998
yield unit_state.clear_resolved()
1001
unit_state.watch_resolved(callback)
1002
yield unit_state.set_resolved(NO_HOOKS)
1003
yield unit_state.clear_resolved()
1005
yield self.poke_zk()
1007
self.assertEqual(len(results), 3)
1008
self.assertIdentical(results.pop(0), False)
1009
self.assertIdentical(results.pop(0), False)
1010
self.assertEqual(results.pop(0).type_name, "created")
1013
(yield unit_state.get_resolved()), None)
1016
def test_get_set_clear_relation_resolved(self):
1017
"""The a unit's realtions can be set to resolved to mark a
1018
future transition, with an optional retry flag."""
1020
unit_state = yield self.get_unit_state()
1022
self.assertIdentical((yield unit_state.get_relation_resolved()), None)
1023
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1025
# Trying to set a conflicting raises an error
1026
yield self.assertFailure(
1027
unit_state.set_relation_resolved({"0": NO_HOOKS}),
1028
ServiceUnitRelationResolvedAlreadyEnabled)
1030
# Doing the same thing is fine
1031
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}),
1033
# Its fine to put in new values
1034
yield unit_state.set_relation_resolved({"21": RETRY_HOOKS})
1035
yield self.assertEqual(
1036
(yield unit_state.get_relation_resolved()),
1037
{"0": RETRY_HOOKS, "21": RETRY_HOOKS})
1039
yield unit_state.clear_relation_resolved()
1040
self.assertIdentical((yield unit_state.get_relation_resolved()), None)
1041
yield unit_state.clear_relation_resolved()
1043
yield self.assertFailure(
1044
unit_state.set_relation_resolved(True), ValueError)
1045
yield self.assertFailure(
1046
unit_state.set_relation_resolved(None), ValueError)
1049
def test_watch_relation_resolved(self):
1050
"""A unit resolved watch can be instituted on a permanent basis."""
1051
unit_state = yield self.get_unit_state()
1055
def callback(value):
1056
results.append(value)
1058
unit_state.watch_relation_resolved(callback)
1059
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1060
yield unit_state.clear_relation_resolved()
1061
yield unit_state.set_relation_resolved({"0": NO_HOOKS})
1063
yield self.poke_zk()
1065
self.assertEqual(len(results), 4)
1066
self.assertIdentical(results.pop(0), False)
1067
self.assertEqual(results.pop(0).type_name, "created")
1068
self.assertEqual(results.pop(0).type_name, "deleted")
1069
self.assertEqual(results.pop(0).type_name, "created")
1072
(yield unit_state.get_relation_resolved()),
1076
def test_watch_relation_resolved_processes_current_state(self):
1077
"""The watch method returns only after processing the current state."""
1078
unit_state = yield self.get_unit_state()
1083
def callback(value):
1084
results.append((yield unit_state.get_relation_resolved()))
1085
yield unit_state.watch_relation_resolved(callback)
1086
self.assertTrue(results)
1089
def test_stop_watch_relation_resolved(self):
1090
"""A unit resolved watch can be instituted on a permanent basis."""
1091
unit_state = yield self.get_unit_state()
1095
def callback(value):
1096
results.append(value)
1098
if len(results) == 1:
1101
if len(results) == 3:
1104
unit_state.watch_relation_resolved(callback)
1105
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1106
yield unit_state.clear_relation_resolved()
1107
yield self.poke_zk()
1108
self.assertEqual(len(results), 1)
1110
unit_state.watch_relation_resolved(callback)
1111
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1112
yield unit_state.clear_relation_resolved()
1113
yield self.poke_zk()
1114
self.assertEqual(len(results), 3)
1115
self.assertIdentical(results.pop(0), False)
1116
self.assertIdentical(results.pop(0), False)
1117
self.assertEqual(results.pop(0).type_name, "created")
1120
(yield unit_state.get_relation_resolved()), None)
1123
def test_watch_resolved_slow_callback(self):
1124
"""A slow watch callback is still invoked serially."""
1125
unit_state = yield self.get_unit_state()
1127
callbacks = [Deferred() for i in range(5)]
1133
results.append(value)
1134
yield callbacks[len(results) - 1]
1135
contents.append((yield unit_state.get_resolved()))
1137
callbacks[0].callback(True)
1138
yield unit_state.watch_resolved(watch)
1140
# These get collapsed into a single event
1141
yield unit_state.set_resolved(RETRY_HOOKS)
1142
yield unit_state.clear_resolved()
1143
yield self.poke_zk()
1145
# Verify the callback hasn't completed
1146
self.assertEqual(len(results), 2)
1147
self.assertEqual(len(contents), 1)
1150
callbacks[1].callback(True)
1151
yield self.poke_zk()
1153
# Verify result counts
1154
self.assertEqual(len(results), 3)
1155
self.assertEqual(len(contents), 2)
1157
# Verify result values. Even though we have created event, the
1158
# setting retrieved shows the hook is not enabled.
1159
self.assertEqual(results[-1].type_name, "deleted")
1160
self.assertEqual(contents[-1], None)
1162
yield unit_state.set_resolved(NO_HOOKS)
1163
callbacks[2].callback(True)
1164
yield self.poke_zk()
1166
self.assertEqual(len(results), 4)
1167
self.assertEqual(contents[-1], {"retry": NO_HOOKS})
1169
# Clear out any pending activity.
1170
yield self.poke_zk()
1173
def test_watch_relation_resolved_slow_callback(self):
1174
"""A slow watch callback is still invoked serially."""
1175
unit_state = yield self.get_unit_state()
1177
callbacks = [Deferred() for i in range(5)]
1183
results.append(value)
1184
yield callbacks[len(results) - 1]
1185
contents.append((yield unit_state.get_relation_resolved()))
1187
callbacks[0].callback(True)
1188
yield unit_state.watch_relation_resolved(watch)
1190
# These get collapsed into a single event
1191
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1192
yield unit_state.clear_relation_resolved()
1193
yield self.poke_zk()
1195
# Verify the callback hasn't completed
1196
self.assertEqual(len(results), 2)
1197
self.assertEqual(len(contents), 1)
1200
callbacks[1].callback(True)
1201
yield self.poke_zk()
1203
# Verify result counts
1204
self.assertEqual(len(results), 3)
1205
self.assertEqual(len(contents), 2)
1207
# Verify result values. Even though we have created event, the
1208
# setting retrieved shows the hook is not enabled.
1209
self.assertEqual(results[-1].type_name, "deleted")
1210
self.assertEqual(contents[-1], None)
1212
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1213
callbacks[2].callback(True)
1214
yield self.poke_zk()
1216
self.assertEqual(len(results), 4)
1217
self.assertEqual(contents[-1], {"0": RETRY_HOOKS})
1219
# Clear out any pending activity.
1220
yield self.poke_zk()
1223
def test_set_and_clear_upgrade_flag(self):
1224
"""An upgrade flag can be set on a unit."""
1227
unit_state = yield self.get_unit_state()
1228
upgrade_flag = yield unit_state.get_upgrade_flag()
1229
self.assertEqual(upgrade_flag, False)
1232
yield unit_state.set_upgrade_flag()
1233
upgrade_flag = yield unit_state.get_upgrade_flag()
1234
self.assertEqual(upgrade_flag, {'force': False})
1236
# Attempting to set multiple times is an error if the values
1238
yield self.assertFailure(
1239
unit_state.set_upgrade_flag(force=True),
1240
ServiceUnitUpgradeAlreadyEnabled)
1241
self.assertEqual(upgrade_flag, {'force': False})
1244
yield unit_state.clear_upgrade_flag()
1245
upgrade_flag = yield unit_state.get_upgrade_flag()
1246
self.assertEqual(upgrade_flag, False)
1248
# Can be cleared multiple times
1249
yield unit_state.clear_upgrade_flag()
1250
upgrade_flag = yield unit_state.get_upgrade_flag()
1251
self.assertEqual(upgrade_flag, False)
1253
# A empty node present is not problematic
1254
yield self.client.create(unit_state._upgrade_flag_path, "")
1255
upgrade_flag = yield unit_state.get_upgrade_flag()
1256
self.assertEqual(upgrade_flag, False)
1258
yield unit_state.set_upgrade_flag(force=True)
1259
upgrade_flag = yield unit_state.get_upgrade_flag()
1260
self.assertEqual(upgrade_flag, {"force": True})
1263
def test_watch_upgrade_flag_once(self):
1264
"""An upgrade watch can be set to notified of presence and changes."""
1265
unit_state = yield self.get_unit_state()
1266
yield unit_state.set_upgrade_flag()
1270
def callback(value):
1271
results.append(value)
1273
unit_state.watch_upgrade_flag(callback, permanent=False)
1274
yield unit_state.clear_upgrade_flag()
1275
yield unit_state.set_upgrade_flag(force=True)
1276
yield self.sleep(0.1)
1277
yield self.poke_zk()
1278
self.assertEqual(len(results), 2)
1279
self.assertIdentical(results.pop(0), True)
1280
self.assertIdentical(results.pop().type_name, "deleted")
1283
(yield unit_state.get_upgrade_flag()),
1287
def test_watch_upgrade_processes_current_state(self):
1288
unit_state = yield self.get_unit_state()
1292
def callback(value):
1293
results.append((yield unit_state.get_upgrade_flag()))
1295
yield unit_state.watch_upgrade_flag(callback)
1296
self.assertTrue(results)
1299
def test_watch_upgrade_flag_permanent(self):
1300
"""An upgrade watch can be instituted on a permanent basis."""
1301
unit_state = yield self.get_unit_state()
1305
def callback(value):
1306
results.append(value)
1308
yield unit_state.watch_upgrade_flag(callback)
1309
self.assertTrue(results)
1310
yield unit_state.set_upgrade_flag()
1311
yield unit_state.clear_upgrade_flag()
1312
yield unit_state.set_upgrade_flag()
1314
yield self.poke_zk()
1316
self.assertEqual(len(results), 4)
1317
self.assertIdentical(results.pop(0), False)
1318
self.assertIdentical(results.pop(0).type_name, "created")
1319
self.assertIdentical(results.pop(0).type_name, "deleted")
1320
self.assertIdentical(results.pop(0).type_name, "created")
1323
(yield unit_state.get_upgrade_flag()),
1327
def test_watch_upgrade_flag_waits_on_slow_callbacks(self):
1328
"""A slow watch callback is still invoked serially."""
1329
unit_state = yield self.get_unit_state()
1331
callbacks = [Deferred() for i in range(5)]
1337
results.append(value)
1338
yield callbacks[len(results) - 1]
1339
contents.append((yield unit_state.get_upgrade_flag()))
1341
yield callbacks[0].callback(True)
1342
yield unit_state.watch_upgrade_flag(watch)
1344
# These get collapsed into a single event
1345
yield unit_state.set_upgrade_flag()
1346
yield unit_state.clear_upgrade_flag()
1347
yield self.poke_zk()
1349
# Verify the callback hasn't completed
1350
self.assertEqual(len(results), 2)
1351
self.assertEqual(len(contents), 1)
1354
callbacks[1].callback(True)
1355
yield self.poke_zk()
1357
# Verify result counts
1358
self.assertEqual(len(results), 3)
1359
self.assertEqual(len(contents), 2)
1361
# Verify result values. Even though we have created event, the
1362
# setting retrieved shows the hook is not enabled.
1363
self.assertEqual(results[-1].type_name, "deleted")
1364
self.assertEqual(contents[-1], False)
1366
yield unit_state.set_upgrade_flag()
1367
yield self.poke_zk()
1369
# Verify the callback hasn't completed
1370
self.assertEqual(len(contents), 2)
1372
callbacks[2].callback(True)
1373
yield self.poke_zk()
1376
self.assertEqual(len(contents), 3)
1377
self.assertEqual(results[-1].type_name, "created")
1378
self.assertEqual(contents[-1], {'force': False})
1380
# Clear out any pending activity.
1381
yield self.poke_zk()
1384
def test_enable_debug_hook(self):
1385
"""Unit hook debugging can be enabled on the unit state."""
1386
unit_state = yield self.get_unit_state()
1387
enabled = yield unit_state.enable_hook_debug(["*"])
1388
self.assertIdentical(enabled, True)
1389
content, stat = yield self.client.get(
1390
"/units/%s/debug" % unit_state.internal_id)
1391
data = yaml.load(content)
1392
self.assertEqual(data, {"debug_hooks": ["*"]})
1395
def test_enable_debug_multiple_named_hooks(self):
1396
"""Unit hook debugging can be enabled for multiple hooks."""
1397
unit_state = yield self.get_unit_state()
1398
enabled = yield unit_state.enable_hook_debug(
1399
["db-relation-broken", "db-relation-changed"])
1401
self.assertIdentical(enabled, True)
1402
content, stat = yield self.client.get(
1403
"/units/%s/debug" % unit_state.internal_id)
1404
data = yaml.load(content)
1407
{"debug_hooks": ["db-relation-broken", "db-relation-changed"]})
1410
def test_enable_debug_all_and_named_is_error(self):
1411
"""Unit hook debugging can be enabled for multiple hooks,
1412
but only if they are all named hooks."""
1413
unit_state = yield self.get_unit_state()
1415
error = yield self.assertFailure(
1416
unit_state.enable_hook_debug(["*", "db-relation-changed"]),
1420
"Ambigious to debug all hooks and named hooks "
1421
"['*', 'db-relation-changed']")
1424
def test_enable_debug_requires_sequence(self):
1425
"""The enable hook debug only accepts a sequences of names.
1427
unit_state = yield self.get_unit_state()
1429
error = yield self.assertFailure(
1430
unit_state.enable_hook_debug(None),
1432
self.assertEquals(str(error), "Hook names must be a list: got None")
1435
def test_enable_named_debug_hook(self):
1436
"""Unit hook debugging can be enabled on for a named hook."""
1437
unit_state = yield self.get_unit_state()
1438
enabled = yield unit_state.enable_hook_debug(
1439
["db-relation-changed"])
1440
self.assertIdentical(enabled, True)
1441
content, stat = yield self.client.get(
1442
"/units/%s/debug" % unit_state.internal_id)
1443
data = yaml.load(content)
1444
self.assertEqual(data, {"debug_hooks": ["db-relation-changed"]})
1447
def test_enable_debug_hook_pre_existing(self):
1448
"""Attempting to enable debug on a unit state already being debugged
1449
raises an exception.
1451
unit_state = yield self.get_unit_state()
1452
yield unit_state.enable_hook_debug(["*"])
1453
error = yield self.assertFailure(unit_state.enable_hook_debug(["*"]),
1454
ServiceUnitDebugAlreadyEnabled)
1456
str(error), "Service unit 'wordpress/0' is already in debug mode.")
1459
def test_enable_debug_hook_lifetime(self):
1460
"""A debug hook setting is only active for the lifetime of the client
1463
unit_state = yield self.get_unit_state()
1464
yield unit_state.enable_hook_debug(["*"])
1465
exists = yield self.client.exists(
1466
"/units/%s/debug" % unit_state.internal_id)
1467
self.assertTrue(exists)
1468
yield self.client.close()
1469
self.client = self.get_zookeeper_client()
1470
yield self.client.connect()
1471
exists = yield self.client.exists(
1472
"/units/%s/debug" % unit_state.internal_id)
1473
self.assertFalse(exists)
1476
def test_watch_debug_hook_once(self):
1477
"""A watch can be set to notified of presence and changes."""
1478
unit_state = yield self.get_unit_state()
1479
yield unit_state.enable_hook_debug(["*"])
1483
def callback(value):
1484
results.append(value)
1486
yield unit_state.watch_hook_debug(callback, permanent=False)
1487
yield unit_state.clear_hook_debug()
1488
yield unit_state.enable_hook_debug(["*"])
1489
yield self.poke_zk()
1490
self.assertEqual(len(results), 2)
1491
self.assertIdentical(results.pop(0), True)
1492
self.assertIdentical(results.pop().type_name, "deleted")
1495
(yield unit_state.get_hook_debug()),
1496
{"debug_hooks": ["*"]})
1499
def test_watch_debug_hook_processes_current_state(self):
1500
"""A hook debug watch can be instituted on a permanent basis."""
1501
unit_state = yield self.get_unit_state()
1506
def callback(value):
1507
results.append((yield unit_state.get_hook_debug()))
1509
yield unit_state.watch_hook_debug(callback)
1510
self.assertTrue(results)
1513
def test_watch_debug_hook_permanent(self):
1514
"""A hook debug watch can be instituted on a permanent basis."""
1515
unit_state = yield self.get_unit_state()
1519
def callback(value):
1520
results.append(value)
1522
yield unit_state.watch_hook_debug(callback)
1523
yield unit_state.enable_hook_debug(["*"])
1524
yield unit_state.clear_hook_debug()
1525
yield unit_state.enable_hook_debug(["*"])
1527
yield self.poke_zk()
1529
self.assertEqual(len(results), 4)
1530
self.assertIdentical(results.pop(0), False)
1531
self.assertIdentical(results.pop(0).type_name, "created")
1532
self.assertIdentical(results.pop(0).type_name, "deleted")
1533
self.assertIdentical(results.pop(0).type_name, "created")
1536
(yield unit_state.get_hook_debug()),
1537
{"debug_hooks": ["*"]})
1540
def test_watch_debug_hook_waits_on_slow_callbacks(self):
1541
"""A slow watch callback is still invoked serially."""
1543
unit_state = yield self.get_unit_state()
1545
callbacks = [Deferred() for i in range(5)]
1551
results.append(value)
1552
yield callbacks[len(results) - 1]
1553
contents.append((yield unit_state.get_hook_debug()))
1555
callbacks[0].callback(True) # Finish the current state processing
1556
yield unit_state.watch_hook_debug(watch)
1558
# These get collapsed into a single event
1559
yield unit_state.enable_hook_debug(["*"])
1560
yield unit_state.clear_hook_debug()
1561
yield self.poke_zk()
1563
# Verify the callback hasn't completed
1564
self.assertEqual(len(results), 2)
1565
self.assertEqual(len(contents), 1)
1568
callbacks[1].callback(True)
1569
yield self.poke_zk()
1571
# Verify result counts
1572
self.assertEqual(len(results), 3)
1573
self.assertEqual(len(contents), 2)
1575
# Verify result values. Even though we have created event, the
1576
# setting retrieved shows the hook is not enabled.
1577
self.assertEqual(results[-1].type_name, "deleted")
1578
self.assertEqual(contents[-1], None)
1580
yield unit_state.enable_hook_debug(["*"])
1581
yield self.poke_zk()
1583
# Verify the callback hasn't completed
1584
self.assertEqual(len(contents), 2)
1586
callbacks[2].callback(True)
1587
yield self.poke_zk()
1590
self.assertEqual(len(contents), 3)
1591
self.assertEqual(results[-1].type_name, "created")
1592
self.assertEqual(contents[-1], {"debug_hooks": ["*"]})
1594
# Clear out any pending activity.
1595
yield self.poke_zk()
1598
def test_service_unit_agent(self):
1599
"""A service unit state has an associated unit agent."""
1600
service_state = yield self.service_state_manager.add_service_state(
1601
"wordpress", self.charm_state, dummy_constraints)
1602
unit_state = yield service_state.add_unit_state()
1603
exists_d, watch_d = unit_state.watch_agent()
1604
exists = yield exists_d
1605
self.assertFalse(exists)
1606
yield unit_state.connect_agent()
1607
event = yield watch_d
1608
self.assertEqual(event.type_name, "created")
1609
self.assertEqual(event.path,
1610
"/units/%s/agent" % unit_state.internal_id)
1613
def test_get_charm_id(self):
1614
"""A service unit knows its charm id"""
1615
service_state = yield self.service_state_manager.add_service_state(
1616
"wordpress", self.charm_state, dummy_constraints)
1617
unit_state = yield service_state.add_unit_state()
1618
unit_charm = yield unit_state.get_charm_id()
1619
service_charm = yield service_state.get_charm_id()
1621
(self.charm_state.id == unit_charm == service_charm))
1624
def test_set_charm_id(self):
1625
"""A service unit charm can be set and is validated when set."""
1626
service_state = yield self.service_state_manager.add_service_state(
1627
"wordpress", self.charm_state, dummy_constraints)
1628
unit_state = yield service_state.add_unit_state()
1629
yield self.assertFailure(
1630
unit_state.set_charm_id("abc"), CharmURLError)
1631
yield self.assertFailure(
1632
unit_state.set_charm_id("abc:foobar-a"), CharmURLError)
1633
yield self.assertFailure(
1634
unit_state.set_charm_id(None), CharmURLError)
1635
charm_id = "local:series/name-1"
1636
yield unit_state.set_charm_id(charm_id)
1637
value = yield unit_state.get_charm_id()
1638
self.assertEqual(charm_id, value)
1641
def test_add_unit_state_combines_constraints(self):
1642
"""Constraints are inherited both from juju defaults and service"""
1643
service_state = yield self.service_state_manager.add_service_state(
1644
"wordpress", self.charm_state,
1645
dummy_cs.parse(["arch=arm", "mem=1G"]))
1646
unit_state = yield service_state.add_unit_state()
1647
constraints = yield unit_state.get_constraints()
1649
"arch": "arm", "cpu": 1, "mem": 1024,
1650
"provider-type": "dummy", "ubuntu-series": "series"}
1651
self.assertEquals(constraints, expected)
1654
def test_unassign_unit_from_machine_without_being_assigned(self):
1656
When unassigning a machine from a unit, it is possible that
1657
the machine has not been previously assigned, or that it
1658
was assigned but the state changed beneath us. In either
1659
case, the end state is the intended state, so we simply
1660
move forward without any errors here, to avoid having to
1661
handle the extra complexity of dealing with the concurrency
1664
service_state = yield self.service_state_manager.add_service_state(
1665
"wordpress", self.charm_state, dummy_constraints)
1666
unit_state = yield service_state.add_unit_state()
1668
yield unit_state.unassign_from_machine()
1670
topology = yield self.get_topology()
1672
topology.get_service_unit_machine(service_state.internal_id,
1673
unit_state.internal_id),
1676
machine_id = yield unit_state.get_assigned_machine_id()
1677
self.assertEqual(machine_id, None)
1680
def test_assign_unit_to_machine_again_fails(self):
1682
Trying to assign a machine to an already assigned unit
1683
should fail, unless we're assigning to precisely the same
1684
machine, in which case it's no big deal.
1686
service_state = yield self.service_state_manager.add_service_state(
1687
"wordpress", self.charm_state, dummy_constraints)
1688
unit_state = yield service_state.add_unit_state()
1689
machine_state0 = yield self.machine_state_manager.add_machine_state(
1691
machine_state1 = yield self.machine_state_manager.add_machine_state(
1694
yield unit_state.assign_to_machine(machine_state0)
1696
# Assigning again to the same machine is a NOOP, so nothing
1697
# terrible should happen if we let it go through.
1698
yield unit_state.assign_to_machine(machine_state0)
1701
yield unit_state.assign_to_machine(machine_state1)
1702
except ServiceUnitStateMachineAlreadyAssigned, e:
1703
self.assertEquals(e.unit_name, "wordpress/0")
1705
self.fail("Error not raised")
1707
machine_id = yield unit_state.get_assigned_machine_id()
1708
self.assertEqual(machine_id, 0)
1711
def test_unassign_unit_from_machine_with_changing_state(self):
1712
service_state = yield self.service_state_manager.add_service_state(
1713
"wordpress", self.charm_state, dummy_constraints)
1714
unit_state = yield service_state.add_unit_state()
1716
yield self.remove_service_unit(service_state.internal_id,
1717
unit_state.internal_id)
1719
d = unit_state.unassign_from_machine()
1720
yield self.assertFailure(d, StateChanged)
1722
d = unit_state.get_assigned_machine_id()
1723
yield self.assertFailure(d, StateChanged)
1725
yield self.remove_service(service_state.internal_id)
1727
d = unit_state.unassign_from_machine()
1728
yield self.assertFailure(d, StateChanged)
1730
d = unit_state.get_assigned_machine_id()
1731
yield self.assertFailure(d, StateChanged)
1734
def test_assign_unit_to_unused_machine(self):
1735
"""Verify that unused machines can be assigned to when their machine
1736
constraints match the service unit's."""
1737
yield self.machine_state_manager.add_machine_state(
1739
mysql_service_state = yield self.add_service_from_charm("mysql")
1740
mysql_unit_state = yield mysql_service_state.add_unit_state()
1741
mysql_machine_state = yield \
1742
self.machine_state_manager.add_machine_state(series_constraints)
1743
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1744
yield self.service_state_manager.remove_service_state(
1745
mysql_service_state)
1746
wordpress_service_state = yield self.add_service_from_charm(
1748
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1749
yield wordpress_unit_state.assign_to_unused_machine()
1751
(yield self.get_topology()).get_machines(),
1752
["machine-0000000000", "machine-0000000001"])
1753
yield self.assert_machine_assignments("wordpress", [1])
1756
def test_assign_unit_to_unused_machine_bad_constraints(self):
1757
"""Verify that unused machines do not get allocated service units with
1758
non-matching constraints."""
1759
yield self.machine_state_manager.add_machine_state(
1761
mysql_service_state = yield self.add_service_from_charm("mysql")
1762
mysql_unit_state = yield mysql_service_state.add_unit_state()
1763
mysql_machine_state = yield \
1764
self.machine_state_manager.add_machine_state(
1766
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1767
yield self.service_state_manager.remove_service_state(
1768
mysql_service_state)
1769
other_constraints = dummy_cs.parse(["arch=arm"])
1770
wordpress_service_state = yield self.add_service_from_charm(
1771
"wordpress", constraints=other_constraints.with_series("series"))
1772
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1773
yield self.assertFailure(
1774
wordpress_unit_state.assign_to_unused_machine(),
1777
(yield self.get_topology()).get_machines(),
1778
["machine-0000000000", "machine-0000000001"])
1779
yield self.assert_machine_assignments("wordpress", [None])
1782
def test_assign_unit_to_unused_machine_with_changing_state_service(self):
1783
"""Verify `StateChanged` raised if service is manipulated during reuse.
1785
yield self.machine_state_manager.add_machine_state(
1787
mysql_service_state = yield self.add_service_from_charm("mysql")
1788
mysql_unit_state = yield mysql_service_state.add_unit_state()
1789
mysql_machine_state = yield self.machine_state_manager.add_machine_state(
1791
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1792
yield self.service_state_manager.remove_service_state(
1793
mysql_service_state)
1794
wordpress_service_state = yield self.add_service_from_charm(
1796
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1797
yield self.remove_service(wordpress_service_state.internal_id)
1798
yield self.assertFailure(
1799
wordpress_unit_state.assign_to_unused_machine(), StateChanged)
1802
def test_assign_unit_to_unused_machine_with_changing_state_service_unit(self):
1803
"Verify `StateChanged` raised if unit is manipulated during reuse."""
1804
yield self.machine_state_manager.add_machine_state(
1806
mysql_service_state = yield self.add_service_from_charm("mysql")
1807
mysql_unit_state = yield mysql_service_state.add_unit_state()
1808
mysql_machine_state = yield self.machine_state_manager.add_machine_state(
1810
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1811
yield self.service_state_manager.remove_service_state(
1812
mysql_service_state)
1813
wordpress_service_state = yield self.add_service_from_charm(
1815
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1816
yield self.remove_service_unit(
1817
wordpress_service_state.internal_id,
1818
wordpress_unit_state.internal_id)
1819
yield self.assertFailure(
1820
wordpress_unit_state.assign_to_unused_machine(), StateChanged)
1823
def test_assign_unit_to_unused_machine_only_machine_zero(self):
1824
"""Verify when the only available machine is machine 0"""
1825
yield self.machine_state_manager.add_machine_state(
1827
wordpress_service_state = yield self.add_service_from_charm(
1829
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1830
yield self.assertFailure(
1831
wordpress_unit_state.assign_to_unused_machine(),
1835
def test_assign_unit_to_unused_machine_none_available(self):
1836
"""Verify when there are no unused machines"""
1837
yield self.machine_state_manager.add_machine_state(
1839
mysql_service_state = yield self.add_service_from_charm("mysql")
1840
mysql_unit_state = yield mysql_service_state.add_unit_state()
1841
mysql_machine_state = yield self.machine_state_manager.add_machine_state(
1843
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1844
yield self.assert_machine_assignments("mysql", [1])
1845
wordpress_service_state = yield self.add_service_from_charm(
1847
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1848
yield self.assertFailure(
1849
wordpress_unit_state.assign_to_unused_machine(),
1853
def test_watch_relations_processes_current_state(self):
1855
The watch method returns only after processing initial state.
1857
Note the callback is only invoked if there are changes
1858
requiring processing.
1860
service_state = yield self.add_service("wordpress")
1861
yield self.add_relation(
1862
"rel-type", "global", [service_state, "name", "role"])
1866
def callback(*args):
1867
results.append(True)
1869
yield service_state.watch_relation_states(callback)
1870
self.assertTrue(results)
1873
def test_watch_relations_when_being_created(self):
1875
We can watch relations before we have any.
1877
service_state = yield self.add_service("wordpress")
1879
wait_callback = [Deferred() for i in range(5)]
1882
def watch_relations(old_relations, new_relations):
1883
calls.append((old_relations, new_relations))
1884
wait_callback[len(calls) - 1].callback(True)
1887
service_state.watch_relation_states(watch_relations)
1889
# Callback is still untouched
1890
self.assertEquals(calls, [])
1892
# add a service relation and wait for the callback
1893
relation_state = yield self.add_relation(
1894
"rel-type", "global", [service_state, "name", "role"])
1895
yield wait_callback[1]
1898
self.assertEquals(len(calls), 2)
1899
old_relations, new_relations = calls[1]
1900
self.assertFalse(old_relations)
1901
self.assertEquals(new_relations[0].relation_role, "role")
1903
# add a new relation with the service assigned to it.
1904
relation_state2 = yield self.add_relation(
1905
"rel-type2", "global", [service_state, "app", "server"])
1906
yield wait_callback[2]
1908
self.assertEquals(len(calls), 3)
1909
old_relations, new_relations = calls[2]
1910
self.assertEquals([r.internal_relation_id for r in old_relations],
1911
[relation_state.internal_id])
1912
self.assertEquals([r.internal_relation_id for r in new_relations],
1913
[relation_state.internal_id,
1914
relation_state2.internal_id])
1917
def test_watch_relations_may_defer(self):
1919
The watch relations callback may return a deferred so that
1920
it performs some its logic asynchronously. In this case, it must
1921
not be called a second time before its postponed logic is finished
1924
wait_callback = [Deferred() for i in range(5)]
1925
finish_callback = [Deferred() for i in range(5)]
1929
def watch_relations(old_relations, new_relations):
1930
calls.append((old_relations, new_relations))
1931
wait_callback[len(calls) - 1].callback(True)
1932
return finish_callback[len(calls) - 1]
1934
service_state = yield self.add_service("s-1")
1935
service_state.watch_relation_states(watch_relations)
1937
# Shouldn't have any callbacks yet.
1938
self.assertEquals(calls, [])
1940
# Assign to a relation.
1941
yield self.add_relation("rel-type", "global",
1942
(service_state, "name", "role"))
1944
# Hold off until callback is started.
1945
yield wait_callback[0]
1947
# Assign to another relation.
1948
yield self.add_relation("rel-type", "global",
1949
(service_state, "name2", "role"))
1951
# Give a chance for something bad to happen.
1952
yield self.sleep(0.3)
1954
# Ensure we still have a single call.
1955
self.assertEquals(len(calls), 1)
1957
# Allow the first call to be completed, and wait on the
1959
finish_callback[0].callback(None)
1960
yield wait_callback[1]
1961
finish_callback[1].callback(None)
1963
# We should have the second change now.
1964
self.assertEquals(len(calls), 2)
1967
def test_get_relation_endpoints_service_name(self):
1968
"""Test getting endpoints with descriptor ``<service name>``"""
1969
yield self.add_service_from_charm("wordpress")
1971
(yield self.service_state_manager.get_relation_endpoints(
1973
[RelationEndpoint("wordpress", "varnish", "cache", "client"),
1974
RelationEndpoint("wordpress", "mysql", "db", "client"),
1975
RelationEndpoint("wordpress", "http", "url", "server"),
1976
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
1977
yield self.add_service_from_charm("riak")
1979
(yield self.service_state_manager.get_relation_endpoints(
1981
[RelationEndpoint("riak", "http", "admin", "server"),
1982
RelationEndpoint("riak", "http", "endpoint", "server"),
1983
RelationEndpoint("riak", "riak", "ring", "peer"),
1984
RelationEndpoint("riak", "juju-info", "juju-info", "server")])
1987
def test_get_relation_endpoints_service_name_relation_name(self):
1988
"""Test getting endpoints with ``<service name:relation name>``"""
1989
yield self.add_service_from_charm("wordpress")
1991
(yield self.service_state_manager.get_relation_endpoints(
1993
[RelationEndpoint("wordpress", "http", "url", "server"),
1994
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
1996
(yield self.service_state_manager.get_relation_endpoints(
1998
[RelationEndpoint("wordpress", "mysql", "db", "client"),
1999
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
2001
(yield self.service_state_manager.get_relation_endpoints(
2002
"wordpress:cache")),
2003
[RelationEndpoint("wordpress", "varnish", "cache", "client"),
2004
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
2005
yield self.add_service_from_charm("riak")
2007
(yield self.service_state_manager.get_relation_endpoints(
2009
[RelationEndpoint("riak", "riak", "ring", "peer"),
2010
RelationEndpoint("riak", "juju-info", "juju-info", "server")])
2013
def test_descriptor_for_services_without_charms(self):
2014
"""Test with services that have no corresponding charms defined"""
2015
yield self.add_service("nocharm")
2016
# verify we get the implicit interface
2018
(yield self.service_state_manager.get_relation_endpoints(
2020
[RelationEndpoint("nocharm",
2021
"juju-info", "juju-info", "server")])
2024
(yield self.service_state_manager.get_relation_endpoints(
2025
"nocharm:nonsense")),
2026
[RelationEndpoint("nocharm",
2027
"juju-info", "juju-info", "server")])
2030
def test_descriptor_for_missing_service(self):
2031
"""Test with a service that is not in the topology"""
2032
yield self.assertFailure(
2033
self.service_state_manager.get_relation_endpoints(
2035
ServiceStateNotFound)
2038
def test_bad_descriptors(self):
2039
"""Test that the descriptors meet the minimum naming standards"""
2040
yield self.assertFailure(
2041
self.service_state_manager.get_relation_endpoints("a:b:c"),
2043
yield self.assertFailure(
2044
self.service_state_manager.get_relation_endpoints(""),
2048
def test_join_descriptors_service_name(self):
2049
"""Test descriptor of the form ``<service name>`"""
2050
yield self.add_service_from_charm("wordpress")
2051
yield self.add_service_from_charm("mysql")
2053
(yield self.service_state_manager.join_descriptors(
2054
"wordpress", "mysql")),
2055
[(RelationEndpoint("wordpress", "mysql", "db", "client"),
2056
RelationEndpoint("mysql", "mysql", "server", "server"))])
2057
# symmetric - note the pair has rotated
2059
(yield self.service_state_manager.join_descriptors(
2060
"mysql", "wordpress")),
2061
[(RelationEndpoint("mysql", "mysql", "server", "server"),
2062
RelationEndpoint("wordpress", "mysql", "db", "client"))])
2063
yield self.add_service_from_charm("varnish")
2065
(yield self.service_state_manager.join_descriptors(
2066
"wordpress", "varnish")),
2067
[(RelationEndpoint("wordpress", "varnish", "cache", "client"),
2068
RelationEndpoint("varnish", "varnish", "webcache", "server"))])
2071
def test_join_descriptors_service_name_relation_name(self):
2072
"""Test joining descriptors ``<service name:relation name>``"""
2073
yield self.add_service_from_charm("wordpress")
2074
yield self.add_service_from_charm("mysql")
2076
(yield self.service_state_manager.join_descriptors(
2077
"wordpress:db", "mysql")),
2078
[(RelationEndpoint("wordpress", "mysql", "db", "client"),
2079
RelationEndpoint("mysql", "mysql", "server", "server"))])
2081
(yield self.service_state_manager.join_descriptors(
2082
"mysql:server", "wordpress")),
2083
[(RelationEndpoint("mysql", "mysql", "server", "server"),
2084
RelationEndpoint("wordpress", "mysql", "db", "client"))])
2086
(yield self.service_state_manager.join_descriptors(
2087
"mysql:server", "wordpress:db")),
2088
[(RelationEndpoint("mysql", "mysql", "server", "server"),
2089
RelationEndpoint("wordpress", "mysql", "db", "client"))])
2091
yield self.add_service_from_charm("varnish")
2093
(yield self.service_state_manager.join_descriptors(
2094
"wordpress:cache", "varnish")),
2095
[(RelationEndpoint("wordpress", "varnish", "cache", "client"),
2096
RelationEndpoint("varnish", "varnish", "webcache", "server"))])
2098
(yield self.service_state_manager.join_descriptors(
2099
"wordpress:cache", "varnish:webcache")),
2100
[(RelationEndpoint("wordpress", "varnish", "cache", "client"),
2101
RelationEndpoint("varnish", "varnish", "webcache", "server"))])
2104
def test_join_peer_descriptors(self):
2105
"""Test joining of peer relation descriptors"""
2106
yield self.add_service_from_charm("riak")
2108
(yield self.service_state_manager.join_descriptors(
2110
[(RelationEndpoint("riak", "riak", "ring", "peer"),
2111
RelationEndpoint("riak", "riak", "ring", "peer"))])
2113
(yield self.service_state_manager.join_descriptors(
2114
"riak:ring", "riak")),
2115
[(RelationEndpoint("riak", "riak", "ring", "peer"),
2116
RelationEndpoint("riak", "riak", "ring", "peer"))])
2118
(yield self.service_state_manager.join_descriptors(
2119
"riak:ring", "riak:ring")),
2120
[(RelationEndpoint("riak", "riak", "ring", "peer"),
2121
RelationEndpoint("riak", "riak", "ring", "peer"))])
2123
(yield self.service_state_manager.join_descriptors(
2124
"riak:no-ring", "riak:ring")),
2128
def test_join_descriptors_no_common_relation(self):
2129
"""Test joining of descriptors that do not share a relation"""
2130
yield self.add_service_from_charm("mysql")
2131
yield self.add_service_from_charm("riak")
2132
yield self.add_service_from_charm("wordpress")
2133
yield self.add_service_from_charm("varnish")
2135
(yield self.service_state_manager.join_descriptors(
2139
(yield self.service_state_manager.join_descriptors(
2140
"mysql:server", "riak:ring")),
2143
(yield self.service_state_manager.join_descriptors(
2144
"varnish", "mysql")),
2147
(yield self.service_state_manager.join_descriptors(
2148
"riak:ring", "riak:admin")),
2151
(yield self.service_state_manager.join_descriptors(
2152
"riak", "wordpress")),
2156
def test_join_descriptors_no_service_state(self):
2157
"""Test joining of nonexistent services"""
2158
yield self.add_service_from_charm("wordpress")
2159
yield self.assertFailure(
2160
self.service_state_manager.join_descriptors("wordpress", "nosuch"),
2161
ServiceStateNotFound)
2162
yield self.assertFailure(
2163
self.service_state_manager.join_descriptors("notyet", "nosuch"),
2164
ServiceStateNotFound)
2167
def test_watch_services_initial_callback(self):
2168
"""Watch service processes initial state before returning.
2170
Note the callback is only executed if there is some meaningful state
2175
def callback(*args):
2176
results.append(True)
2178
yield self.service_state_manager.watch_service_states(callback)
2179
yield self.add_service("wordpress")
2180
yield self.poke_zk()
2181
self.assertTrue(results)
2184
def test_watch_services_when_being_created(self):
2186
It should be possible to start watching services even
2187
before they are created. In this case, the callback will
2188
be made when it's actually introduced.
2190
wait_callback = [Deferred() for i in range(10)]
2194
def watch_services(old_services, new_services):
2195
calls.append((old_services, new_services))
2196
wait_callback[len(calls) - 1].callback(True)
2199
self.service_state_manager.watch_service_states(watch_services)
2201
# Callback is still untouched.
2202
self.assertEquals(calls, [])
2204
# Add a service, and wait for callback.
2205
yield self.add_service("wordpress")
2206
yield wait_callback[0]
2208
# The first callback must have been fired, and it must have None
2209
# as the first argument because that's the first service seen.
2210
self.assertEquals(len(calls), 1)
2211
old_services, new_services = calls[0]
2212
self.assertEquals(old_services, set())
2213
self.assertEquals(new_services, set(["wordpress"]))
2215
# Add a service again.
2216
yield self.add_service("mysql")
2217
yield wait_callback[1]
2219
# Now the watch callback must have been fired with two
2220
# different service sets. The old one, and the new one.
2221
self.assertEquals(len(calls), 2)
2222
old_services, new_services = calls[1]
2223
self.assertEquals(old_services, set(["wordpress"]))
2224
self.assertEquals(new_services, set(["mysql", "wordpress"]))
2227
def test_watch_services_may_defer(self):
2229
The watch services callback may return a deferred so that it
2230
performs some of its logic asynchronously. In this case, it
2231
must not be called a second time before its postponed logic
2232
is finished completely.
2234
wait_callback = [Deferred() for i in range(10)]
2235
finish_callback = [Deferred() for i in range(10)]
2239
def watch_services(old_services, new_services):
2240
calls.append((old_services, new_services))
2241
wait_callback[len(calls) - 1].callback(True)
2242
return finish_callback[len(calls) - 1]
2245
self.service_state_manager.watch_service_states(watch_services)
2247
# Create the service.
2248
yield self.add_service("wordpress")
2250
# Hold off until callback is started.
2251
yield wait_callback[0]
2253
# Add another service.
2254
yield self.add_service("mysql")
2256
# Ensure we still have a single call.
2257
self.assertEquals(len(calls), 1)
2259
# Allow the first call to be completed, and wait on the
2261
finish_callback[0].callback(None)
2262
yield wait_callback[1]
2263
finish_callback[1].callback(None)
2265
# We should have the second change now.
2266
self.assertEquals(len(calls), 2)
2267
old_services, new_services = calls[1]
2268
self.assertEquals(old_services, set(["wordpress"]))
2269
self.assertEquals(new_services, set(["mysql", "wordpress"]))
2272
def test_watch_services_with_changing_topology(self):
2274
If the topology changes in an unrelated way, the services
2275
watch callback should not be called with two equal
2278
wait_callback = [Deferred() for i in range(10)]
2282
def watch_services(old_services, new_services):
2283
calls.append((old_services, new_services))
2284
wait_callback[len(calls) - 1].callback(True)
2287
self.service_state_manager.watch_service_states(watch_services)
2289
# Callback is still untouched.
2290
self.assertEquals(calls, [])
2292
# Add a service, and wait for callback.
2293
yield self.add_service("wordpress")
2294
yield wait_callback[0]
2296
# Now change the topology in an unrelated way.
2297
yield self.machine_state_manager.add_machine_state(
2300
# Add a service again.
2301
yield self.add_service("mysql")
2302
yield wait_callback[1]
2304
# But it *shouldn't* have happened.
2305
self.assertEquals(len(calls), 2)
2308
def test_watch_service_units_initial_callback(self):
2309
"""Watch service unit processes initial state before returning.
2311
Note the callback is only executed if there is some meaningful state
2316
def callback(*args):
2317
results.append(True)
2319
service_state = yield self.add_service("wordpress")
2320
yield service_state.watch_service_unit_states(callback)
2321
yield service_state.add_unit_state()
2322
yield self.poke_zk()
2323
self.assertTrue(results)
2326
def test_watch_service_units_when_being_created(self):
2328
It should be possible to start watching service units even
2329
before they are created. In this case, the callback will be
2330
made when it's actually introduced.
2332
wait_callback = [Deferred() for i in range(10)]
2336
def watch_service_units(old_service_units, new_service_units):
2337
calls.append((old_service_units, new_service_units))
2338
wait_callback[len(calls) - 1].callback(True)
2341
service_state = yield self.add_service("wordpress")
2342
service_state.watch_service_unit_states(watch_service_units)
2344
# Callback is still untouched.
2345
self.assertEquals(calls, [])
2347
# Add a service unit, and wait for callback.
2348
yield service_state.add_unit_state()
2350
yield wait_callback[0]
2352
# The first callback must have been fired, and it must have None
2353
# as the first argument because that's the first service seen.
2354
self.assertEquals(len(calls), 1)
2355
old_service_units, new_service_units = calls[0]
2356
self.assertEquals(old_service_units, set())
2357
self.assertEquals(new_service_units, set(["wordpress/0"]))
2359
# Add another service unit.
2360
yield service_state.add_unit_state()
2361
yield wait_callback[1]
2363
# Now the watch callback must have been fired with two
2364
# different service sets. The old one, and the new one.
2365
self.assertEquals(len(calls), 2)
2366
old_service_units, new_service_units = calls[1]
2367
self.assertEquals(old_service_units, set(["wordpress/0"]))
2368
self.assertEquals(new_service_units, set(["wordpress/0", "wordpress/1"]))
2371
def test_watch_service_units_may_defer(self):
2373
The watch service units callback may return a deferred so that
2374
it performs some of its logic asynchronously. In this case,
2375
it must not be called a second time before its postponed logic
2376
is finished completely.
2378
wait_callback = [Deferred() for i in range(10)]
2379
finish_callback = [Deferred() for i in range(10)]
2383
def watch_service_units(old_service_units, new_service_units):
2384
calls.append((old_service_units, new_service_units))
2385
wait_callback[len(calls) - 1].callback(True)
2386
return finish_callback[len(calls) - 1]
2389
service_state = yield self.add_service("wordpress")
2390
service_state.watch_service_unit_states(watch_service_units)
2392
# Create the service unit.
2393
yield service_state.add_unit_state()
2395
# Hold off until callback is started.
2396
yield wait_callback[0]
2398
# Add another service unit.
2399
yield service_state.add_unit_state()
2401
# Ensure we still have a single call.
2402
self.assertEquals(len(calls), 1)
2404
# Allow the first call to be completed, and wait on the
2406
finish_callback[0].callback(None)
2407
yield wait_callback[1]
2408
finish_callback[1].callback(None)
2410
# We should have the second change now.
2411
self.assertEquals(len(calls), 2)
2412
old_service_units, new_service_units = calls[1]
2413
self.assertEquals(old_service_units, set(["wordpress/0"]))
2415
new_service_units, set(["wordpress/0", "wordpress/1"]))
2418
def test_watch_service_units_with_changing_topology(self):
2420
If the topology changes in an unrelated way, the services
2421
watch callback should not be called with two equal
2424
wait_callback = [Deferred() for i in range(10)]
2428
def watch_service_units(old_service_units, new_service_units):
2429
calls.append((old_service_units, new_service_units))
2430
wait_callback[len(calls) - 1].callback(True)
2433
service_state = yield self.add_service("wordpress")
2434
service_state.watch_service_unit_states(watch_service_units)
2436
# Callback is still untouched.
2437
self.assertEquals(calls, [])
2439
# Add a service, and wait for callback.
2440
yield service_state.add_unit_state()
2441
yield wait_callback[0]
2443
# Now change the topology in an unrelated way.
2444
yield self.machine_state_manager.add_machine_state(
2447
# Add a service again.
2448
yield service_state.add_unit_state()
2449
yield wait_callback[1]
2451
# But it *shouldn't* have happened.
2452
self.assertEquals(len(calls), 2)
2455
def test_service_config_get_set(self):
2456
"""Validate that we can set and get service config options."""
2457
wordpress = yield self.add_service_from_charm("wordpress")
2459
# attempt to get the initialized service state
2460
config = yield wordpress.get_config()
2462
# the initial state is empty
2463
self.assertEqual(config, {"blog-title": "My Title"})
2465
# behaves as a normal dict
2466
self.assertRaises(KeyError, config.__getitem__, "missing")
2468
# various ways to set state
2469
config.update(dict(alpha="beta", one="two"))
2470
config["another"] = "value"
2473
yield config.write()
2475
# we should be able to read the config and see the same values
2476
# (in this case it would be the cached object)
2477
config2 = yield wordpress.get_config()
2478
self.assertEqual(config2, {"alpha": "beta",
2481
"blog-title": "My Title"})
2483
# now set a non-string value and recover it
2484
config2["number"] = 1
2485
config2["one"] = None
2486
yield config2.write()
2489
self.assertEquals(config["number"], 1)
2490
self.assertEquals(config["one"], None)
2493
def test_service_config_get_returns_new(self):
2494
"""Validate that we can set and get service config options."""
2495
wordpress = yield self.add_service_from_charm("wordpress")
2497
# attempt to get the initialized service state
2498
config = yield wordpress.get_config()
2499
config.update({"foo": "bar"})
2500
# Defaults come through
2501
self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"})
2503
config2 = yield wordpress.get_config()
2504
self.assertEqual(config2, {"blog-title": "My Title"})
2506
yield config.write()
2507
self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"})
2509
# Config2 is still empty (a different YAML State), with charm defaults.
2510
self.assertEqual(config2, {"blog-title": "My Title"})
2512
yield config2.read()
2513
self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"})
2515
# The default was never written to storage.
2516
data, stat = yield self.client.get(
2517
"/services/%s/config" % wordpress.internal_id)
2518
self.assertEqual(yaml.load(data), {"foo": "bar"})
2521
def test_get_charm_state(self):
2522
wordpress = yield self.add_service_from_charm("wordpress")
2523
charm = yield wordpress.get_charm_state()
2525
self.assertEqual(charm.name, "wordpress")
2526
metadata = yield charm.get_metadata()
2527
self.assertEqual(metadata.summary, "Blog engine")
166
def test_add_service(self):
168
Adding a service state should register it in zookeeper,
169
including the requested charm id.
171
yield self.service_state_manager.add_service_state(
172
"wordpress", self.charm_state, dummy_constraints)
173
yield self.service_state_manager.add_service_state(
174
"mysql", self.charm_state, dummy_constraints)
175
children = yield self.client.get_children("/services")
177
self.assertEquals(sorted(children), [
178
"service-0000000000", "service-0000000001"])
180
content, stat = yield self.client.get("/services/service-0000000000")
181
details = yaml.load(content)
182
self.assertTrue(details)
183
self.assertEquals(details.get("charm"), "local:series/dummy-1")
184
self.assertFalse(isinstance(details.get("charm"), unicode))
185
self.assertEquals(details.get("constraints"), series_constraints.data)
187
topology = yield self.get_topology()
188
self.assertEquals(topology.find_service_with_name("wordpress"),
189
"service-0000000000")
190
self.assertEquals(topology.find_service_with_name("mysql"),
191
"service-0000000001")
194
def test_add_service_with_duplicated_name(self):
196
If a service is added with a duplicated name, a meaningful
197
error should be raised.
199
yield self.service_state_manager.add_service_state(
200
"wordpress", self.charm_state, dummy_constraints)
203
yield self.service_state_manager.add_service_state(
204
"wordpress", self.charm_state, dummy_constraints)
205
except ServiceStateNameInUse, e:
206
self.assertEquals(e.service_name, "wordpress")
208
self.fail("Error not raised")
211
def test_get_service_and_check_attributes(self):
213
Getting a service state should be possible, and the service
214
state identification should be available through its
217
yield self.service_state_manager.add_service_state(
218
"wordpress", self.charm_state, dummy_constraints)
219
service_state = yield self.service_state_manager.get_service_state(
221
self.assertEquals(service_state.service_name, "wordpress")
222
self.assertEquals(service_state.internal_id, "service-0000000000")
225
def test_get_service_not_found(self):
227
Getting a service state which is not available should errback
231
yield self.service_state_manager.get_service_state("wordpress")
232
except ServiceStateNotFound, e:
233
self.assertEquals(e.service_name, "wordpress")
235
self.fail("Error not raised")
237
def test_get_unit_state(self):
238
"""A unit state can be retrieved by name from the service manager."""
239
self.assertFailure(self.service_state_manager.get_unit_state(
240
"wordpress/1"), ServiceStateNotFound)
242
self.assertFailure(self.service_state_manager.get_unit_state(
243
"wordpress1"), ServiceUnitStateNotFound)
245
wordpress_state = yield self.service_state_manager.add_service_state(
246
"wordpress", self.charm_state, dummy_constraints)
248
self.assertFailure(self.service_state_manager.get_unit_state(
249
"wordpress/1"), ServiceUnitStateNotFound)
251
wordpress_unit = wordpress_state.add_unit_state()
253
unit_state = yield self.service_state_manager.get_unit_state(
256
self.assertEqual(unit_state.internal_id, wordpress_unit.internal_id)
259
def test_get_service_charm_id(self):
261
The service state should make its respective charm id available.
263
yield self.service_state_manager.add_service_state(
264
"wordpress", self.charm_state, dummy_constraints)
265
service_state = yield self.service_state_manager.get_service_state(
267
charm_id = yield service_state.get_charm_id()
268
self.assertEquals(charm_id, "local:series/dummy-1")
271
def test_set_service_charm_id(self):
273
The service state should allow its charm id to be set.
275
yield self.service_state_manager.add_service_state(
276
"wordpress", self.charm_state, dummy_constraints)
277
service_state = yield self.service_state_manager.get_service_state(
279
yield service_state.set_charm_id("local:series/dummy-2")
280
charm_id = yield service_state.get_charm_id()
281
self.assertEquals(charm_id, "local:series/dummy-2")
284
def test_get_service_constraints(self):
285
"""The service state should make constraints available"""
286
initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"])
287
yield self.service_state_manager.add_service_state(
288
"wordpress", self.charm_state, initial_constraints)
289
service_state = yield self.service_state_manager.get_service_state(
291
constraints = yield service_state.get_constraints()
293
constraints, initial_constraints.with_series("series"))
296
def test_get_service_constraints_inherits(self):
297
"""The service constraints should be combined with the environment's"""
298
yield self.push_env_constraints("arch=arm", "cpu=32")
299
service_constraints = dummy_cs.parse(["cpu=256"])
300
yield self.service_state_manager.add_service_state(
301
"wordpress", self.charm_state, service_constraints)
302
service_state = yield self.service_state_manager.get_service_state(
304
constraints = yield service_state.get_constraints()
305
expected_base = dummy_cs.parse(["arch=arm", "cpu=256"])
306
self.assertEquals(constraints, expected_base.with_series("series"))
309
def test_get_missing_service_constraints(self):
311
Nodes created before the constraints mechanism was added should have
314
yield self.client.delete("/constraints")
315
yield self.service_state_manager.add_service_state(
316
"wordpress", self.charm_state, dummy_constraints)
317
service_state = yield self.service_state_manager.get_service_state(
319
path = "/services/" + service_state.internal_id
320
node = YAMLState(self.client, path)
322
del node["constraints"]
324
constraints = yield service_state.get_constraints()
325
self.assertEquals(constraints.data, {})
328
def test_get_missing_unit_constraints(self):
330
Nodes created before the constraints mechanism was added should have
333
yield self.service_state_manager.add_service_state(
334
"wordpress", self.charm_state, dummy_constraints)
335
service_state = yield self.service_state_manager.get_service_state(
337
unit_state = yield service_state.add_unit_state()
338
path = "/units/" + unit_state.internal_id
339
node = YAMLState(self.client, path)
341
del node["constraints"]
343
constraints = yield unit_state.get_constraints()
344
self.assertEquals(constraints.data, {})
347
def test_set_service_constraints(self):
348
"""The service state should make constraints available for change"""
349
initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"])
350
yield self.service_state_manager.add_service_state(
351
"wordpress", self.charm_state, initial_constraints)
352
service_state = yield self.service_state_manager.get_service_state(
354
new_constraints = dummy_cs.parse(["mem=2G", "arch=arm"])
355
yield service_state.set_constraints(new_constraints)
356
retrieved_constraints = yield service_state.get_constraints()
358
retrieved_constraints, new_constraints.with_series("series"))
361
def test_remove_service_state(self):
363
A service state can be removed along with its relations, units,
366
service_state = yield self.service_state_manager.add_service_state(
367
"wordpress", self.charm_state, dummy_constraints)
369
relation_state = yield self.add_relation(
370
"rel-type2", "global", [service_state, "app", "server"])
372
unit_state = yield service_state.add_unit_state()
373
machine_state = yield self.machine_state_manager.add_machine_state(
375
yield unit_state.assign_to_machine(machine_state)
377
yield self.service_state_manager.remove_service_state(service_state)
379
topology = yield self.get_topology()
380
self.assertFalse(topology.has_relation(relation_state.internal_id))
381
self.assertFalse(topology.has_service(service_state.internal_id))
382
self.assertFalse(topology.get_service_units_in_machine(
383
machine_state.internal_id))
385
exists = yield self.client.exists(
386
"/services/%s" % service_state.internal_id)
387
self.assertFalse(exists)
390
def test_add_service_unit_and_check_attributes(self):
392
A service state should enable adding a new service unit
393
under it, and again the unit should offer attributes allowing
396
service_state0 = yield self.service_state_manager.add_service_state(
397
"wordpress", self.charm_state, dummy_constraints)
398
service_state1 = yield self.service_state_manager.add_service_state(
399
"mysql", self.charm_state, dummy_constraints)
401
unit_state0 = yield service_state0.add_unit_state()
402
unit_state1 = yield service_state1.add_unit_state()
403
unit_state2 = yield service_state0.add_unit_state()
404
unit_state3 = yield service_state1.add_unit_state()
406
children = yield self.client.get_children("/units")
407
self.assertEquals(sorted(children), [
408
"unit-0000000000", "unit-0000000001",
409
"unit-0000000002", "unit-0000000003"])
411
self.assertEquals(unit_state0.service_name, "wordpress")
412
self.assertEquals(unit_state0.internal_id, "unit-0000000000")
413
self.assertEquals(unit_state0.unit_name, "wordpress/0")
415
self.assertEquals(unit_state1.service_name, "mysql")
416
self.assertEquals(unit_state1.internal_id, "unit-0000000001")
417
self.assertEquals(unit_state1.unit_name, "mysql/0")
419
self.assertEquals(unit_state2.service_name, "wordpress")
420
self.assertEquals(unit_state2.internal_id, "unit-0000000002")
421
self.assertEquals(unit_state2.unit_name, "wordpress/1")
423
self.assertEquals(unit_state3.service_name, "mysql")
424
self.assertEquals(unit_state3.internal_id, "unit-0000000003")
425
self.assertEquals(unit_state3.unit_name, "mysql/1")
427
topology = yield self.get_topology()
430
topology.has_service_unit("service-0000000000",
433
topology.has_service_unit("service-0000000001",
436
topology.has_service_unit("service-0000000000",
439
topology.has_service_unit("service-0000000001",
442
def get_presence_path(
443
self, relation_state, relation_role, unit_state, container=None):
444
container = container.internal_id if container else None
445
presence_path = "/".join(filter(None, [
447
relation_state.internal_id,
450
unit_state.internal_id]))
454
def test_add_service_unit_with_container(self):
456
Validate adding units with containers specified and recovering that.
458
mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info",
460
logging_ep = RelationEndpoint("logging", "juju-info", "juju-info",
461
"client", "container")
463
logging_charm_state = yield self.get_subordinate_charm()
464
self.assertTrue(logging_charm_state.is_subordinate())
465
log_state = yield self.service_state_manager.add_service_state(
466
"logging", logging_charm_state, dummy_constraints)
467
mysql_state = yield self.service_state_manager.add_service_state(
468
"mysql", self.charm_state, dummy_constraints)
470
relation_state, service_states = (yield
471
self.relation_state_manager.add_relation_state(
472
mysql_ep, logging_ep))
474
unit_state1 = yield mysql_state.add_unit_state()
475
unit_state0 = yield log_state.add_unit_state(container=unit_state1)
477
unit_state3 = yield mysql_state.add_unit_state()
478
unit_state2 = yield log_state.add_unit_state(container=unit_state3)
480
self.assertEquals((yield unit_state1.get_container()), None)
481
self.assertEquals((yield unit_state0.get_container()), unit_state1)
482
self.assertEquals((yield unit_state2.get_container()), unit_state3)
483
self.assertEquals((yield unit_state3.get_container()), None)
485
for unit_state in (unit_state1, unit_state3):
486
yield unit_state.set_private_address(
488
unit_state.unit_name.replace("/", "-")))
490
# construct the proper relation state
491
mystate = pick_attr(service_states, relation_role="server")
492
logstate = pick_attr(service_states, relation_role="client")
493
yield logstate.add_unit_state(unit_state0)
494
yield logstate.add_unit_state(unit_state2)
496
yield mystate.add_unit_state(unit_state1)
497
yield mystate.add_unit_state(unit_state3)
500
def verify_container(relation_state, service_relation_state,
501
unit_state, container):
502
presence_path = self.get_presence_path(
504
service_relation_state.relation_role,
508
content, stat = yield self.client.get(presence_path)
509
self.assertTrue(stat)
510
self.assertEqual(content, '')
511
# verify the node data on the relation role nodes
512
role_path = os.path.dirname(presence_path)
514
content, stat = yield self.client.get(role_path)
515
self.assertTrue(stat)
516
node_info = yaml.load(content)
520
service_relation_state.relation_name)
523
service_relation_state.relation_role)
525
settings_path = os.path.dirname(
526
os.path.dirname(presence_path)) + "/settings/" + \
527
unit_state.internal_id
528
content, stat = yield self.client.get(settings_path)
529
self.assertTrue(stat)
530
settings_info = yaml.load(content)
532
# Verify that private address was set
533
# we verify the content elsewhere
534
self.assertTrue(settings_info["private-address"])
536
# verify all the units are constructed as expected
537
# first the client roles with another container
538
yield verify_container(relation_state, logstate,
539
unit_state0, unit_state1)
541
yield verify_container(relation_state, logstate,
542
unit_state2, unit_state3)
544
# and now the principals (which are their own relation containers)
545
yield verify_container(relation_state, logstate,
546
unit_state0, unit_state1)
548
yield verify_container(relation_state, mystate,
549
unit_state1, unit_state1)
552
def test_get_container_no_principal(self):
553
"""Get container should handle no principal."""
554
mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info",
556
logging_ep = RelationEndpoint("logging", "juju-info", "juju-info",
557
"client", "container")
559
logging_charm_state = yield self.get_subordinate_charm()
560
self.assertTrue(logging_charm_state.is_subordinate())
561
log_state = yield self.service_state_manager.add_service_state(
562
"logging", logging_charm_state, dummy_constraints)
563
mysql_state = yield self.service_state_manager.add_service_state(
564
"mysql", self.charm_state, dummy_constraints)
566
relation_state, service_states = (yield
567
self.relation_state_manager.add_relation_state(
568
mysql_ep, logging_ep))
570
unit_state1 = yield mysql_state.add_unit_state()
571
unit_state0 = yield log_state.add_unit_state(container=unit_state1)
573
unit_state3 = yield mysql_state.add_unit_state()
574
unit_state2 = yield log_state.add_unit_state(container=unit_state3)
576
self.assertEquals((yield unit_state1.get_container()), None)
577
self.assertEquals((yield unit_state0.get_container()), unit_state1)
578
self.assertEquals((yield unit_state2.get_container()), unit_state3)
579
self.assertEquals((yield unit_state3.get_container()), None)
581
# now remove a principal node and test again
583
yield mysql_state.remove_unit_state(unit_state1)
584
container = yield unit_state0.get_container()
585
self.assertEquals(container, None)
587
# the other pair is still fine
588
self.assertEquals((yield unit_state2.get_container()), unit_state3)
589
self.assertEquals((yield unit_state3.get_container()), None)
593
def test_add_service_unit_with_changing_state(self):
595
When adding a service unit, there's a chance that the
596
service will go away mid-way through. Rather than blowing
597
up randomly, a nice error should be raised.
599
service_state = yield self.service_state_manager.add_service_state(
600
"wordpress", self.charm_state, dummy_constraints)
602
yield self.remove_service(service_state.internal_id)
604
d = service_state.add_unit_state()
605
yield self.assertFailure(d, StateChanged)
608
def test_get_unit_names(self):
609
"""A service's units names are retrievable."""
610
service_state = yield self.service_state_manager.add_service_state(
611
"wordpress", self.charm_state, dummy_constraints)
615
unit_state = yield service_state.add_unit_state()
616
expected_names.append(unit_state.unit_name)
618
unit_names = yield service_state.get_unit_names()
619
self.assertEqual(unit_names, expected_names)
622
def test_remove_service_unit(self):
623
"""Removing a service unit removes all state associated.
625
service_state = yield self.service_state_manager.add_service_state(
626
"wordpress", self.charm_state, dummy_constraints)
628
unit_state = yield service_state.add_unit_state()
630
# Assign to a machine
631
machine_state = yield self.machine_state_manager.add_machine_state(
633
yield unit_state.assign_to_machine(machine_state)
634
# Connect a unit agent
635
yield unit_state.connect_agent()
637
# Now try and destroy it.
638
yield service_state.remove_unit_state(unit_state)
640
# Verify destruction.
641
topology = yield self.get_topology()
642
self.assertTrue(topology.has_service(service_state.internal_id))
643
self.assertFalse(topology.has_service_unit(
644
service_state.internal_id, unit_state.internal_id))
646
exists = yield self.client.exists("/units/%s" % unit_state.internal_id)
647
self.assertFalse(exists)
649
def test_remove_service_unit_nonexistant(self):
650
"""Removing a non existant service unit, is fine."""
652
service_state = yield self.service_state_manager.add_service_state(
653
"wordpress", self.charm_state, dummy_constraints)
654
unit_state = yield service_state.add_unit_state()
655
yield service_state.remove_unit_state(unit_state)
656
yield service_state.remove_unit_state(unit_state)
659
def test_get_all_service_states(self):
660
services = yield self.service_state_manager.get_all_service_states()
661
self.assertFalse(services)
663
yield self.service_state_manager.add_service_state(
664
"wordpress", self.charm_state, dummy_constraints)
665
services = yield self.service_state_manager.get_all_service_states()
666
self.assertEquals(len(services), 1)
668
yield self.service_state_manager.add_service_state(
669
"mysql", self.charm_state, dummy_constraints)
670
services = yield self.service_state_manager.get_all_service_states()
671
self.assertEquals(len(services), 2)
674
def test_get_service_unit(self):
676
Getting back service units should be possible using the
679
service_state0 = yield self.service_state_manager.add_service_state(
680
"wordpress", self.charm_state, dummy_constraints)
681
service_state1 = yield self.service_state_manager.add_service_state(
682
"mysql", self.charm_state, dummy_constraints)
684
yield service_state0.add_unit_state()
685
yield service_state1.add_unit_state()
686
yield service_state0.add_unit_state()
687
yield service_state1.add_unit_state()
689
unit_state0 = yield service_state0.get_unit_state("wordpress/0")
690
unit_state1 = yield service_state1.get_unit_state("mysql/0")
691
unit_state2 = yield service_state0.get_unit_state("wordpress/1")
692
unit_state3 = yield service_state1.get_unit_state("mysql/1")
694
self.assertEquals(unit_state0.internal_id, "unit-0000000000")
695
self.assertEquals(unit_state1.internal_id, "unit-0000000001")
696
self.assertEquals(unit_state2.internal_id, "unit-0000000002")
697
self.assertEquals(unit_state3.internal_id, "unit-0000000003")
699
self.assertEquals(unit_state0.unit_name, "wordpress/0")
700
self.assertEquals(unit_state1.unit_name, "mysql/0")
701
self.assertEquals(unit_state2.unit_name, "wordpress/1")
702
self.assertEquals(unit_state3.unit_name, "mysql/1")
705
def test_get_all_unit_states(self):
706
service_state0 = yield self.service_state_manager.add_service_state(
707
"wordpress", self.charm_state, dummy_constraints)
708
service_state1 = yield self.service_state_manager.add_service_state(
709
"mysql", self.charm_state, dummy_constraints)
711
yield service_state0.add_unit_state()
712
yield service_state1.add_unit_state()
713
yield service_state0.add_unit_state()
714
yield service_state1.add_unit_state()
716
unit_state0 = yield service_state0.get_unit_state("wordpress/0")
717
unit_state1 = yield service_state1.get_unit_state("mysql/0")
718
unit_state2 = yield service_state0.get_unit_state("wordpress/1")
719
unit_state3 = yield service_state1.get_unit_state("mysql/1")
721
wordpress_units = yield service_state0.get_all_unit_states()
723
set(wordpress_units), set((unit_state0, unit_state2)))
725
mysql_units = yield service_state1.get_all_unit_states()
726
self.assertEquals(set(mysql_units), set((unit_state1, unit_state3)))
729
def test_get_all_unit_states_with_changing_state(self):
731
When getting the service unit states, there's a chance that
732
the service will go away mid-way through. Rather than blowing
733
up randomly, a nice error should be raised.
735
service_state = yield self.service_state_manager.add_service_state(
736
"wordpress", self.charm_state, dummy_constraints)
737
yield service_state.add_unit_state()
738
unit_state = (yield service_state.get_all_unit_states())[0]
739
self.assertEqual(unit_state.unit_name, "wordpress/0")
740
yield self.remove_service(service_state.internal_id)
741
yield self.assertFailure(
742
service_state.get_all_unit_states(), StateChanged)
745
def test_set_functions(self):
746
wordpress = yield self.service_state_manager.add_service_state(
747
"wordpress", self.charm_state, dummy_constraints)
748
mysql = yield self.service_state_manager.add_service_state(
749
"mysql", self.charm_state, dummy_constraints)
751
s1 = yield self.service_state_manager.get_service_state(
753
s2 = yield self.service_state_manager.get_service_state(
755
self.assertEquals(hash(s1), hash(wordpress))
756
self.assertEquals(hash(s2), hash(mysql))
758
self.assertNotEqual(s1, object())
759
self.assertNotEqual(s1, s2)
761
self.assertEquals(s1, wordpress)
762
self.assertEquals(s2, mysql)
764
us0 = yield wordpress.add_unit_state()
765
us1 = yield wordpress.add_unit_state()
767
unit_state0 = yield wordpress.get_unit_state("wordpress/0")
768
unit_state1 = yield wordpress.get_unit_state("wordpress/1")
770
self.assertEquals(us0, unit_state0)
771
self.assertEquals(us1, unit_state1)
772
self.assertEquals(hash(us1), hash(unit_state1))
774
self.assertNotEqual(us0, object())
775
self.assertNotEqual(us0, us1)
778
def test_get_service_unit_not_found(self):
780
Attempting to retrieve a non-existent service unit should
781
result in an errback.
783
service_state0 = yield self.service_state_manager.add_service_state(
784
"wordpress", self.charm_state, dummy_constraints)
785
service_state1 = yield self.service_state_manager.add_service_state(
786
"mysql", self.charm_state, dummy_constraints)
788
# Add some state in a different service to make it a little
789
# bit more prone to breaking in case of errors.
790
yield service_state1.add_unit_state()
793
yield service_state0.get_unit_state("wordpress/0")
794
except ServiceUnitStateNotFound, e:
795
self.assertEquals(e.unit_name, "wordpress/0")
797
self.fail("Error not raised")
800
def test_get_set_public_address(self):
801
service_state = yield self.service_state_manager.add_service_state(
802
"wordpress", self.charm_state, dummy_constraints)
803
unit_state = yield service_state.add_unit_state()
804
self.assertEqual((yield unit_state.get_public_address()), None)
805
yield unit_state.set_public_address("example.foobar.com")
806
yield self.assertEqual(
807
(yield unit_state.get_public_address()),
808
"example.foobar.com")
811
def test_get_set_private_address(self):
812
service_state = yield self.service_state_manager.add_service_state(
813
"wordpress", self.charm_state, dummy_constraints)
814
unit_state = yield service_state.add_unit_state()
815
self.assertEqual((yield unit_state.get_private_address()), None)
816
yield unit_state.set_private_address("example.local")
817
yield self.assertEqual(
818
(yield unit_state.get_private_address()),
822
def test_get_service_unit_with_changing_state(self):
824
If a service is removed during operation, get_service_unit()
825
should raise a nice error.
827
service_state = yield self.service_state_manager.add_service_state(
828
"wordpress", self.charm_state, dummy_constraints)
830
yield self.remove_service(service_state.internal_id)
832
d = service_state.get_unit_state("wordpress/0")
833
yield self.assertFailure(d, StateChanged)
836
def test_get_service_unit_with_bad_service_name(self):
838
Service unit names contain a service name embedded into
839
them. The service name requested when calling get_unit_state()
840
must match that of the object being used.
842
service_state0 = yield self.service_state_manager.add_service_state(
843
"wordpress", self.charm_state, dummy_constraints)
844
service_state1 = yield self.service_state_manager.add_service_state(
845
"mysql", self.charm_state, dummy_constraints)
847
# Add some state in a different service to make it a little
848
# bit more prone to breaking in case of errors.
849
yield service_state1.add_unit_state()
852
yield service_state0.get_unit_state("mysql/0")
853
except BadServiceStateName, e:
854
self.assertEquals(e.expected_name, "wordpress")
855
self.assertEquals(e.obtained_name, "mysql")
857
self.fail("Error not raised")
860
def test_assign_unit_to_machine(self):
861
service_state = yield self.service_state_manager.add_service_state(
862
"wordpress", self.charm_state, dummy_constraints)
863
unit_state = yield service_state.add_unit_state()
864
machine_state = yield self.machine_state_manager.add_machine_state(
867
yield unit_state.assign_to_machine(machine_state)
869
topology = yield self.get_topology()
872
topology.get_service_unit_machine(service_state.internal_id,
873
unit_state.internal_id),
874
machine_state.internal_id)
877
def test_assign_unit_to_machine_with_changing_state(self):
878
service_state = yield self.service_state_manager.add_service_state(
879
"wordpress", self.charm_state, dummy_constraints)
880
unit_state = yield service_state.add_unit_state()
881
machine_state = yield self.machine_state_manager.add_machine_state(
884
yield self.remove_service_unit(service_state.internal_id,
885
unit_state.internal_id)
887
d = unit_state.assign_to_machine(machine_state)
888
yield self.assertFailure(d, StateChanged)
890
yield self.remove_service(service_state.internal_id)
892
d = unit_state.assign_to_machine(machine_state)
893
yield self.assertFailure(d, StateChanged)
896
def test_unassign_unit_from_machine(self):
897
service_state = yield self.service_state_manager.add_service_state(
898
"wordpress", self.charm_state, dummy_constraints)
899
unit_state = yield service_state.add_unit_state()
900
machine_state = yield self.machine_state_manager.add_machine_state(
903
yield unit_state.assign_to_machine(machine_state)
904
yield unit_state.unassign_from_machine()
906
topology = yield self.get_topology()
908
self.assertEquals(topology.get_service_unit_machine(
909
service_state.internal_id, unit_state.internal_id), None)
912
def test_get_set_clear_resolved(self):
913
"""The a unit can be set to resolved to mark a future transition, with
914
an optional retry flag."""
916
unit_state = yield self.get_unit_state()
918
self.assertIdentical((yield unit_state.get_resolved()), None)
919
yield unit_state.set_resolved(NO_HOOKS)
921
yield self.assertFailure(
922
unit_state.set_resolved(NO_HOOKS),
923
ServiceUnitResolvedAlreadyEnabled)
924
yield self.assertEqual((yield unit_state.get_resolved()),
927
yield unit_state.clear_resolved()
928
self.assertIdentical((yield unit_state.get_resolved()), None)
929
yield unit_state.clear_resolved()
931
yield self.assertFailure(unit_state.set_resolved(None), ValueError)
934
def test_watch_resolved(self):
935
"""A unit resolved watch can be instituted on a permanent basis."""
936
unit_state = yield self.get_unit_state()
941
results.append(value)
943
unit_state.watch_resolved(callback)
944
yield unit_state.set_resolved(RETRY_HOOKS)
945
yield unit_state.clear_resolved()
946
yield unit_state.set_resolved(NO_HOOKS)
950
self.assertEqual(len(results), 4)
951
self.assertIdentical(results.pop(0), False)
952
self.assertEqual(results.pop(0).type_name, "created")
953
self.assertEqual(results.pop(0).type_name, "deleted")
954
self.assertEqual(results.pop(0).type_name, "created")
957
(yield unit_state.get_resolved()),
961
def test_watch_resolved_processes_current_state(self):
962
"""The watch method processes the current state before returning."""
963
unit_state = yield self.get_unit_state()
969
results.append((yield unit_state.get_resolved()))
971
yield unit_state.watch_resolved(callback)
972
self.assertTrue(results)
975
def test_stop_watch_resolved(self):
976
"""A unit resolved watch can be instituted on a permanent basis.
978
However the callback can raise StopWatcher at anytime to stop the watch
980
unit_state = yield self.get_unit_state()
985
results.append(value)
986
if len(results) == 1:
988
if len(results) == 3:
991
unit_state.watch_resolved(callback)
992
yield unit_state.set_resolved(RETRY_HOOKS)
993
yield unit_state.clear_resolved()
996
unit_state.watch_resolved(callback)
997
yield unit_state.set_resolved(NO_HOOKS)
998
yield unit_state.clear_resolved()
1000
yield self.poke_zk()
1002
self.assertEqual(len(results), 3)
1003
self.assertIdentical(results.pop(0), False)
1004
self.assertIdentical(results.pop(0), False)
1005
self.assertEqual(results.pop(0).type_name, "created")
1008
(yield unit_state.get_resolved()), None)
1011
def test_get_set_clear_relation_resolved(self):
1012
"""The a unit's realtions can be set to resolved to mark a
1013
future transition, with an optional retry flag."""
1015
unit_state = yield self.get_unit_state()
1017
self.assertIdentical((yield unit_state.get_relation_resolved()), None)
1018
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1020
# Trying to set a conflicting raises an error
1021
yield self.assertFailure(
1022
unit_state.set_relation_resolved({"0": NO_HOOKS}),
1023
ServiceUnitRelationResolvedAlreadyEnabled)
1025
# Doing the same thing is fine
1026
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}),
1028
# Its fine to put in new values
1029
yield unit_state.set_relation_resolved({"21": RETRY_HOOKS})
1030
yield self.assertEqual(
1031
(yield unit_state.get_relation_resolved()),
1032
{"0": RETRY_HOOKS, "21": RETRY_HOOKS})
1034
yield unit_state.clear_relation_resolved()
1035
self.assertIdentical((yield unit_state.get_relation_resolved()), None)
1036
yield unit_state.clear_relation_resolved()
1038
yield self.assertFailure(
1039
unit_state.set_relation_resolved(True), ValueError)
1040
yield self.assertFailure(
1041
unit_state.set_relation_resolved(None), ValueError)
1044
def test_watch_relation_resolved(self):
1045
"""A unit resolved watch can be instituted on a permanent basis."""
1046
unit_state = yield self.get_unit_state()
1050
def callback(value):
1051
results.append(value)
1053
unit_state.watch_relation_resolved(callback)
1054
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1055
yield unit_state.clear_relation_resolved()
1056
yield unit_state.set_relation_resolved({"0": NO_HOOKS})
1058
yield self.poke_zk()
1060
self.assertEqual(len(results), 4)
1061
self.assertIdentical(results.pop(0), False)
1062
self.assertEqual(results.pop(0).type_name, "created")
1063
self.assertEqual(results.pop(0).type_name, "deleted")
1064
self.assertEqual(results.pop(0).type_name, "created")
1067
(yield unit_state.get_relation_resolved()),
1071
def test_watch_relation_resolved_processes_current_state(self):
1072
"""The watch method returns only after processing the current state."""
1073
unit_state = yield self.get_unit_state()
1078
def callback(value):
1079
results.append((yield unit_state.get_relation_resolved()))
1080
yield unit_state.watch_relation_resolved(callback)
1081
self.assertTrue(results)
1084
def test_stop_watch_relation_resolved(self):
1085
"""A unit resolved watch can be instituted on a permanent basis."""
1086
unit_state = yield self.get_unit_state()
1090
def callback(value):
1091
results.append(value)
1093
if len(results) == 1:
1096
if len(results) == 3:
1099
unit_state.watch_relation_resolved(callback)
1100
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1101
yield unit_state.clear_relation_resolved()
1102
yield self.poke_zk()
1103
self.assertEqual(len(results), 1)
1105
unit_state.watch_relation_resolved(callback)
1106
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1107
yield unit_state.clear_relation_resolved()
1108
yield self.poke_zk()
1109
self.assertEqual(len(results), 3)
1110
self.assertIdentical(results.pop(0), False)
1111
self.assertIdentical(results.pop(0), False)
1112
self.assertEqual(results.pop(0).type_name, "created")
1115
(yield unit_state.get_relation_resolved()), None)
1118
def test_watch_resolved_slow_callback(self):
1119
"""A slow watch callback is still invoked serially."""
1120
unit_state = yield self.get_unit_state()
1122
callbacks = [Deferred() for i in range(5)]
1128
results.append(value)
1129
yield callbacks[len(results) - 1]
1130
contents.append((yield unit_state.get_resolved()))
1132
callbacks[0].callback(True)
1133
yield unit_state.watch_resolved(watch)
1135
# These get collapsed into a single event
1136
yield unit_state.set_resolved(RETRY_HOOKS)
1137
yield unit_state.clear_resolved()
1138
yield self.poke_zk()
1140
# Verify the callback hasn't completed
1141
self.assertEqual(len(results), 2)
1142
self.assertEqual(len(contents), 1)
1145
callbacks[1].callback(True)
1146
yield self.poke_zk()
1148
# Verify result counts
1149
self.assertEqual(len(results), 3)
1150
self.assertEqual(len(contents), 2)
1152
# Verify result values. Even though we have created event, the
1153
# setting retrieved shows the hook is not enabled.
1154
self.assertEqual(results[-1].type_name, "deleted")
1155
self.assertEqual(contents[-1], None)
1157
yield unit_state.set_resolved(NO_HOOKS)
1158
callbacks[2].callback(True)
1159
yield self.poke_zk()
1161
self.assertEqual(len(results), 4)
1162
self.assertEqual(contents[-1], {"retry": NO_HOOKS})
1164
# Clear out any pending activity.
1165
yield self.poke_zk()
1168
def test_watch_relation_resolved_slow_callback(self):
1169
"""A slow watch callback is still invoked serially."""
1170
unit_state = yield self.get_unit_state()
1172
callbacks = [Deferred() for i in range(5)]
1178
results.append(value)
1179
yield callbacks[len(results) - 1]
1180
contents.append((yield unit_state.get_relation_resolved()))
1182
callbacks[0].callback(True)
1183
yield unit_state.watch_relation_resolved(watch)
1185
# These get collapsed into a single event
1186
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1187
yield unit_state.clear_relation_resolved()
1188
yield self.poke_zk()
1190
# Verify the callback hasn't completed
1191
self.assertEqual(len(results), 2)
1192
self.assertEqual(len(contents), 1)
1195
callbacks[1].callback(True)
1196
yield self.poke_zk()
1198
# Verify result counts
1199
self.assertEqual(len(results), 3)
1200
self.assertEqual(len(contents), 2)
1202
# Verify result values. Even though we have created event, the
1203
# setting retrieved shows the hook is not enabled.
1204
self.assertEqual(results[-1].type_name, "deleted")
1205
self.assertEqual(contents[-1], None)
1207
yield unit_state.set_relation_resolved({"0": RETRY_HOOKS})
1208
callbacks[2].callback(True)
1209
yield self.poke_zk()
1211
self.assertEqual(len(results), 4)
1212
self.assertEqual(contents[-1], {"0": RETRY_HOOKS})
1214
# Clear out any pending activity.
1215
yield self.poke_zk()
1218
def test_set_and_clear_upgrade_flag(self):
1219
"""An upgrade flag can be set on a unit."""
1222
unit_state = yield self.get_unit_state()
1223
upgrade_flag = yield unit_state.get_upgrade_flag()
1224
self.assertEqual(upgrade_flag, False)
1227
yield unit_state.set_upgrade_flag()
1228
upgrade_flag = yield unit_state.get_upgrade_flag()
1229
self.assertEqual(upgrade_flag, {"force": False})
1231
# Attempting to set multiple times is an error if the values
1233
yield self.assertFailure(
1234
unit_state.set_upgrade_flag(force=True),
1235
ServiceUnitUpgradeAlreadyEnabled)
1236
self.assertEqual(upgrade_flag, {"force": False})
1239
yield unit_state.clear_upgrade_flag()
1240
upgrade_flag = yield unit_state.get_upgrade_flag()
1241
self.assertEqual(upgrade_flag, False)
1243
# Can be cleared multiple times
1244
yield unit_state.clear_upgrade_flag()
1245
upgrade_flag = yield unit_state.get_upgrade_flag()
1246
self.assertEqual(upgrade_flag, False)
1248
# A empty node present is not problematic
1249
yield self.client.create(unit_state._upgrade_flag_path, "")
1250
upgrade_flag = yield unit_state.get_upgrade_flag()
1251
self.assertEqual(upgrade_flag, False)
1253
yield unit_state.set_upgrade_flag(force=True)
1254
upgrade_flag = yield unit_state.get_upgrade_flag()
1255
self.assertEqual(upgrade_flag, {"force": True})
1258
def test_watch_upgrade_flag_once(self):
1259
"""An upgrade watch can be set to notified of presence and changes."""
1260
unit_state = yield self.get_unit_state()
1261
yield unit_state.set_upgrade_flag()
1265
def callback(value):
1266
results.append(value)
1268
unit_state.watch_upgrade_flag(callback, permanent=False)
1269
yield unit_state.clear_upgrade_flag()
1270
yield unit_state.set_upgrade_flag(force=True)
1271
yield self.sleep(0.1)
1272
yield self.poke_zk()
1273
self.assertEqual(len(results), 2)
1274
self.assertIdentical(results.pop(0), True)
1275
self.assertIdentical(results.pop().type_name, "deleted")
1278
(yield unit_state.get_upgrade_flag()),
1282
def test_watch_upgrade_processes_current_state(self):
1283
unit_state = yield self.get_unit_state()
1287
def callback(value):
1288
results.append((yield unit_state.get_upgrade_flag()))
1290
yield unit_state.watch_upgrade_flag(callback)
1291
self.assertTrue(results)
1294
def test_watch_upgrade_flag_permanent(self):
1295
"""An upgrade watch can be instituted on a permanent basis."""
1296
unit_state = yield self.get_unit_state()
1300
def callback(value):
1301
results.append(value)
1303
yield unit_state.watch_upgrade_flag(callback)
1304
self.assertTrue(results)
1305
yield unit_state.set_upgrade_flag()
1306
yield unit_state.clear_upgrade_flag()
1307
yield unit_state.set_upgrade_flag()
1309
yield self.poke_zk()
1311
self.assertEqual(len(results), 4)
1312
self.assertIdentical(results.pop(0), False)
1313
self.assertIdentical(results.pop(0).type_name, "created")
1314
self.assertIdentical(results.pop(0).type_name, "deleted")
1315
self.assertIdentical(results.pop(0).type_name, "created")
1318
(yield unit_state.get_upgrade_flag()),
1322
def test_watch_upgrade_flag_waits_on_slow_callbacks(self):
1323
"""A slow watch callback is still invoked serially."""
1324
unit_state = yield self.get_unit_state()
1326
callbacks = [Deferred() for i in range(5)]
1332
results.append(value)
1333
yield callbacks[len(results) - 1]
1334
contents.append((yield unit_state.get_upgrade_flag()))
1336
yield callbacks[0].callback(True)
1337
yield unit_state.watch_upgrade_flag(watch)
1339
# These get collapsed into a single event
1340
yield unit_state.set_upgrade_flag()
1341
yield unit_state.clear_upgrade_flag()
1342
yield self.poke_zk()
1344
# Verify the callback hasn't completed
1345
self.assertEqual(len(results), 2)
1346
self.assertEqual(len(contents), 1)
1349
callbacks[1].callback(True)
1350
yield self.poke_zk()
1352
# Verify result counts
1353
self.assertEqual(len(results), 3)
1354
self.assertEqual(len(contents), 2)
1356
# Verify result values. Even though we have created event, the
1357
# setting retrieved shows the hook is not enabled.
1358
self.assertEqual(results[-1].type_name, "deleted")
1359
self.assertEqual(contents[-1], False)
1361
yield unit_state.set_upgrade_flag()
1362
yield self.poke_zk()
1364
# Verify the callback hasn't completed
1365
self.assertEqual(len(contents), 2)
1367
callbacks[2].callback(True)
1368
yield self.poke_zk()
1371
self.assertEqual(len(contents), 3)
1372
self.assertEqual(results[-1].type_name, "created")
1373
self.assertEqual(contents[-1], {"force": False})
1375
# Clear out any pending activity.
1376
yield self.poke_zk()
1379
def test_enable_debug_hook(self):
1380
"""Unit hook debugging can be enabled on the unit state."""
1381
unit_state = yield self.get_unit_state()
1382
enabled = yield unit_state.enable_hook_debug(["*"])
1383
self.assertIdentical(enabled, True)
1384
content, stat = yield self.client.get(
1385
"/units/%s/debug" % unit_state.internal_id)
1386
data = yaml.load(content)
1387
self.assertEqual(data, {"debug_hooks": ["*"]})
1390
def test_enable_debug_multiple_named_hooks(self):
1391
"""Unit hook debugging can be enabled for multiple hooks."""
1392
unit_state = yield self.get_unit_state()
1393
enabled = yield unit_state.enable_hook_debug(
1394
["db-relation-broken", "db-relation-changed"])
1396
self.assertIdentical(enabled, True)
1397
content, stat = yield self.client.get(
1398
"/units/%s/debug" % unit_state.internal_id)
1399
data = yaml.load(content)
1400
self.assertEqual(data, {"debug_hooks":
1401
["db-relation-broken", "db-relation-changed"]})
1404
def test_enable_debug_all_and_named_is_error(self):
1405
"""Unit hook debugging can be enabled for multiple hooks,
1406
but only if they are all named hooks."""
1407
unit_state = yield self.get_unit_state()
1409
error = yield self.assertFailure(
1410
unit_state.enable_hook_debug(["*", "db-relation-changed"]),
1414
"Ambigious to debug all hooks and named hooks "
1415
"['*', 'db-relation-changed']")
1418
def test_enable_debug_requires_sequence(self):
1419
"""The enable hook debug only accepts a sequences of names.
1421
unit_state = yield self.get_unit_state()
1423
error = yield self.assertFailure(
1424
unit_state.enable_hook_debug(None),
1426
self.assertEquals(str(error), "Hook names must be a list: got None")
1429
def test_enable_named_debug_hook(self):
1430
"""Unit hook debugging can be enabled on for a named hook."""
1431
unit_state = yield self.get_unit_state()
1432
enabled = yield unit_state.enable_hook_debug(
1433
["db-relation-changed"])
1434
self.assertIdentical(enabled, True)
1435
content, stat = yield self.client.get(
1436
"/units/%s/debug" % unit_state.internal_id)
1437
data = yaml.load(content)
1438
self.assertEqual(data, {"debug_hooks": ["db-relation-changed"]})
1441
def test_enable_debug_hook_pre_existing(self):
1442
"""Attempting to enable debug on a unit state already being debugged
1443
raises an exception.
1445
unit_state = yield self.get_unit_state()
1446
yield unit_state.enable_hook_debug(["*"])
1447
error = yield self.assertFailure(unit_state.enable_hook_debug(["*"]),
1448
ServiceUnitDebugAlreadyEnabled)
1449
self.assertEquals(str(error),
1450
"Service unit 'wordpress/0' is already in debug mode.")
1453
def test_enable_debug_hook_lifetime(self):
1454
"""A debug hook setting is only active for the lifetime of the client
1457
unit_state = yield self.get_unit_state()
1458
yield unit_state.enable_hook_debug(["*"])
1459
exists = yield self.client.exists(
1460
"/units/%s/debug" % unit_state.internal_id)
1461
self.assertTrue(exists)
1462
yield self.client.close()
1463
self.client = self.get_zookeeper_client()
1464
yield self.client.connect()
1465
exists = yield self.client.exists(
1466
"/units/%s/debug" % unit_state.internal_id)
1467
self.assertFalse(exists)
1470
def test_watch_debug_hook_once(self):
1471
"""A watch can be set to notified of presence and changes."""
1472
unit_state = yield self.get_unit_state()
1473
yield unit_state.enable_hook_debug(["*"])
1477
def callback(value):
1478
results.append(value)
1480
yield unit_state.watch_hook_debug(callback, permanent=False)
1481
yield unit_state.clear_hook_debug()
1482
yield unit_state.enable_hook_debug(["*"])
1483
yield self.poke_zk()
1484
self.assertEqual(len(results), 2)
1485
self.assertIdentical(results.pop(0), True)
1486
self.assertIdentical(results.pop().type_name, "deleted")
1489
(yield unit_state.get_hook_debug()),
1490
{"debug_hooks": ["*"]})
1493
def test_watch_debug_hook_processes_current_state(self):
1494
"""A hook debug watch can be instituted on a permanent basis."""
1495
unit_state = yield self.get_unit_state()
1500
def callback(value):
1501
results.append((yield unit_state.get_hook_debug()))
1503
yield unit_state.watch_hook_debug(callback)
1504
self.assertTrue(results)
1507
def test_watch_debug_hook_permanent(self):
1508
"""A hook debug watch can be instituted on a permanent basis."""
1509
unit_state = yield self.get_unit_state()
1513
def callback(value):
1514
results.append(value)
1516
yield unit_state.watch_hook_debug(callback)
1517
yield unit_state.enable_hook_debug(["*"])
1518
yield unit_state.clear_hook_debug()
1519
yield unit_state.enable_hook_debug(["*"])
1521
yield self.poke_zk()
1523
self.assertEqual(len(results), 4)
1524
self.assertIdentical(results.pop(0), False)
1525
self.assertIdentical(results.pop(0).type_name, "created")
1526
self.assertIdentical(results.pop(0).type_name, "deleted")
1527
self.assertIdentical(results.pop(0).type_name, "created")
1530
(yield unit_state.get_hook_debug()),
1531
{"debug_hooks": ["*"]})
1534
def test_watch_debug_hook_waits_on_slow_callbacks(self):
1535
"""A slow watch callback is still invoked serially."""
1537
unit_state = yield self.get_unit_state()
1539
callbacks = [Deferred() for i in range(5)]
1545
results.append(value)
1546
yield callbacks[len(results) - 1]
1547
contents.append((yield unit_state.get_hook_debug()))
1549
callbacks[0].callback(True) # Finish the current state processing
1550
yield unit_state.watch_hook_debug(watch)
1552
# These get collapsed into a single event
1553
yield unit_state.enable_hook_debug(["*"])
1554
yield unit_state.clear_hook_debug()
1555
yield self.poke_zk()
1557
# Verify the callback hasn't completed
1558
self.assertEqual(len(results), 2)
1559
self.assertEqual(len(contents), 1)
1562
callbacks[1].callback(True)
1563
yield self.poke_zk()
1565
# Verify result counts
1566
self.assertEqual(len(results), 3)
1567
self.assertEqual(len(contents), 2)
1569
# Verify result values. Even though we have created event, the
1570
# setting retrieved shows the hook is not enabled.
1571
self.assertEqual(results[-1].type_name, "deleted")
1572
self.assertEqual(contents[-1], None)
1574
yield unit_state.enable_hook_debug(["*"])
1575
yield self.poke_zk()
1577
# Verify the callback hasn't completed
1578
self.assertEqual(len(contents), 2)
1580
callbacks[2].callback(True)
1581
yield self.poke_zk()
1584
self.assertEqual(len(contents), 3)
1585
self.assertEqual(results[-1].type_name, "created")
1586
self.assertEqual(contents[-1], {"debug_hooks": ["*"]})
1588
# Clear out any pending activity.
1589
yield self.poke_zk()
1592
def test_service_unit_agent(self):
1593
"""A service unit state has an associated unit agent."""
1594
service_state = yield self.service_state_manager.add_service_state(
1595
"wordpress", self.charm_state, dummy_constraints)
1596
unit_state = yield service_state.add_unit_state()
1597
exists_d, watch_d = unit_state.watch_agent()
1598
exists = yield exists_d
1599
self.assertFalse(exists)
1600
yield unit_state.connect_agent()
1601
event = yield watch_d
1602
self.assertEqual(event.type_name, "created")
1603
self.assertEqual(event.path,
1604
"/units/%s/agent" % unit_state.internal_id)
1607
def test_get_charm_id(self):
1608
"""A service unit knows its charm id"""
1609
service_state = yield self.service_state_manager.add_service_state(
1610
"wordpress", self.charm_state, dummy_constraints)
1611
unit_state = yield service_state.add_unit_state()
1612
unit_charm = yield unit_state.get_charm_id()
1613
service_charm = yield service_state.get_charm_id()
1615
(self.charm_state.id == unit_charm == service_charm))
1618
def test_set_charm_id(self):
1619
"""A service unit charm can be set and is validated when set."""
1620
service_state = yield self.service_state_manager.add_service_state(
1621
"wordpress", self.charm_state, dummy_constraints)
1622
unit_state = yield service_state.add_unit_state()
1623
yield self.assertFailure(
1624
unit_state.set_charm_id("abc"), CharmURLError)
1625
yield self.assertFailure(
1626
unit_state.set_charm_id("abc:foobar-a"), CharmURLError)
1627
yield self.assertFailure(
1628
unit_state.set_charm_id(None), CharmURLError)
1629
charm_id = "local:series/name-1"
1630
yield unit_state.set_charm_id(charm_id)
1631
value = yield unit_state.get_charm_id()
1632
self.assertEqual(charm_id, value)
1635
def test_add_unit_state_combines_constraints(self):
1636
"""Constraints are inherited both from juju defaults and service"""
1637
service_state = yield self.service_state_manager.add_service_state(
1638
"wordpress", self.charm_state,
1639
dummy_cs.parse(["arch=arm", "mem=1G"]))
1640
unit_state = yield service_state.add_unit_state()
1641
constraints = yield unit_state.get_constraints()
1643
"arch": "arm", "cpu": 1, "mem": 1024,
1644
"provider-type": "dummy", "ubuntu-series": "series"}
1645
self.assertEquals(constraints, expected)
1648
def test_unassign_unit_from_machine_without_being_assigned(self):
1650
When unassigning a machine from a unit, it is possible that
1651
the machine has not been previously assigned, or that it
1652
was assigned but the state changed beneath us. In either
1653
case, the end state is the intended state, so we simply
1654
move forward without any errors here, to avoid having to
1655
handle the extra complexity of dealing with the concurrency
1658
service_state = yield self.service_state_manager.add_service_state(
1659
"wordpress", self.charm_state, dummy_constraints)
1660
unit_state = yield service_state.add_unit_state()
1662
yield unit_state.unassign_from_machine()
1664
topology = yield self.get_topology()
1665
self.assertEquals(topology.get_service_unit_machine(
1666
service_state.internal_id, unit_state.internal_id), None)
1668
machine_id = yield unit_state.get_assigned_machine_id()
1669
self.assertEqual(machine_id, None)
1672
def test_assign_unit_to_machine_again_fails(self):
1674
Trying to assign a machine to an already assigned unit
1675
should fail, unless we're assigning to precisely the same
1676
machine, in which case it's no big deal.
1678
service_state = yield self.service_state_manager.add_service_state(
1679
"wordpress", self.charm_state, dummy_constraints)
1680
unit_state = yield service_state.add_unit_state()
1681
machine_state0 = yield self.machine_state_manager.add_machine_state(
1683
machine_state1 = yield self.machine_state_manager.add_machine_state(
1686
yield unit_state.assign_to_machine(machine_state0)
1688
# Assigning again to the same machine is a NOOP, so nothing
1689
# terrible should happen if we let it go through.
1690
yield unit_state.assign_to_machine(machine_state0)
1693
yield unit_state.assign_to_machine(machine_state1)
1694
except ServiceUnitStateMachineAlreadyAssigned, e:
1695
self.assertEquals(e.unit_name, "wordpress/0")
1697
self.fail("Error not raised")
1699
machine_id = yield unit_state.get_assigned_machine_id()
1700
self.assertEqual(machine_id, 0)
1703
def test_unassign_unit_from_machine_with_changing_state(self):
1704
service_state = yield self.service_state_manager.add_service_state(
1705
"wordpress", self.charm_state, dummy_constraints)
1706
unit_state = yield service_state.add_unit_state()
1708
yield self.remove_service_unit(service_state.internal_id,
1709
unit_state.internal_id)
1711
d = unit_state.unassign_from_machine()
1712
yield self.assertFailure(d, StateChanged)
1714
d = unit_state.get_assigned_machine_id()
1715
yield self.assertFailure(d, StateChanged)
1717
yield self.remove_service(service_state.internal_id)
1719
d = unit_state.unassign_from_machine()
1720
yield self.assertFailure(d, StateChanged)
1722
d = unit_state.get_assigned_machine_id()
1723
yield self.assertFailure(d, StateChanged)
1726
def test_assign_unit_to_unused_machine(self):
1727
"""Verify that unused machines can be assigned to when their machine
1728
constraints match the service unit's."""
1729
yield self.machine_state_manager.add_machine_state(
1731
mysql_service_state = yield self.add_service_from_charm("mysql")
1732
mysql_unit_state = yield mysql_service_state.add_unit_state()
1733
mysql_machine_state = yield \
1734
self.machine_state_manager.add_machine_state(series_constraints)
1735
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1736
yield self.service_state_manager.remove_service_state(
1737
mysql_service_state)
1738
wordpress_service_state = yield self.add_service_from_charm(
1740
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1741
yield wordpress_unit_state.assign_to_unused_machine()
1743
(yield self.get_topology()).get_machines(),
1744
["machine-0000000000", "machine-0000000001"])
1745
yield self.assert_machine_assignments("wordpress", [1])
1748
def test_assign_unit_to_unused_machine_bad_constraints(self):
1749
"""Verify that unused machines do not get allocated service units with
1750
non-matching constraints."""
1751
yield self.machine_state_manager.add_machine_state(
1753
mysql_service_state = yield self.add_service_from_charm("mysql")
1754
mysql_unit_state = yield mysql_service_state.add_unit_state()
1755
mysql_machine_state = yield \
1756
self.machine_state_manager.add_machine_state(
1758
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1759
yield self.service_state_manager.remove_service_state(
1760
mysql_service_state)
1761
other_constraints = dummy_cs.parse(["arch=arm"])
1762
wordpress_service_state = yield self.add_service_from_charm(
1763
"wordpress", constraints=other_constraints.with_series("series"))
1764
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1765
yield self.assertFailure(
1766
wordpress_unit_state.assign_to_unused_machine(),
1769
(yield self.get_topology()).get_machines(),
1770
["machine-0000000000", "machine-0000000001"])
1771
yield self.assert_machine_assignments("wordpress", [None])
1774
def test_assign_unit_to_unused_machine_with_changing_state_service(self):
1775
"""Verify `StateChanged` raised if service is manipulated during reuse.
1777
yield self.machine_state_manager.add_machine_state(
1779
mysql_service_state = yield self.add_service_from_charm("mysql")
1780
mysql_unit_state = yield mysql_service_state.add_unit_state()
1781
mysql_machine_state = yield self.machine_state_manager.add_machine_state(
1783
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1784
yield self.service_state_manager.remove_service_state(
1785
mysql_service_state)
1786
wordpress_service_state = yield self.add_service_from_charm(
1788
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1789
yield self.remove_service(wordpress_service_state.internal_id)
1790
yield self.assertFailure(
1791
wordpress_unit_state.assign_to_unused_machine(), StateChanged)
1794
def test_assign_unit_to_unused_machine_with_changing_state_service_unit(self):
1795
"Verify `StateChanged` raised if unit is manipulated during reuse."
1796
yield self.machine_state_manager.add_machine_state(
1798
mysql_service_state = yield self.add_service_from_charm("mysql")
1799
mysql_unit_state = yield mysql_service_state.add_unit_state()
1800
mysql_machine_state = yield self.machine_state_manager.add_machine_state(
1802
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1803
yield self.service_state_manager.remove_service_state(
1804
mysql_service_state)
1805
wordpress_service_state = yield self.add_service_from_charm(
1807
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1808
yield self.remove_service_unit(
1809
wordpress_service_state.internal_id,
1810
wordpress_unit_state.internal_id)
1811
yield self.assertFailure(
1812
wordpress_unit_state.assign_to_unused_machine(), StateChanged)
1815
def test_assign_unit_to_unused_machine_only_machine_zero(self):
1816
"""Verify when the only available machine is machine 0"""
1817
yield self.machine_state_manager.add_machine_state(
1819
wordpress_service_state = yield self.add_service_from_charm(
1821
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1822
yield self.assertFailure(
1823
wordpress_unit_state.assign_to_unused_machine(),
1827
def test_assign_unit_to_unused_machine_none_available(self):
1828
"""Verify when there are no unused machines"""
1829
yield self.machine_state_manager.add_machine_state(
1831
mysql_service_state = yield self.add_service_from_charm("mysql")
1832
mysql_unit_state = yield mysql_service_state.add_unit_state()
1833
mysql_machine_state = yield self.machine_state_manager.add_machine_state(
1835
yield mysql_unit_state.assign_to_machine(mysql_machine_state)
1836
yield self.assert_machine_assignments("mysql", [1])
1837
wordpress_service_state = yield self.add_service_from_charm(
1839
wordpress_unit_state = yield wordpress_service_state.add_unit_state()
1840
yield self.assertFailure(
1841
wordpress_unit_state.assign_to_unused_machine(),
1845
def test_watch_relations_processes_current_state(self):
1847
The watch method returns only after processing initial state.
1849
Note the callback is only invoked if there are changes
1850
requiring processing.
1852
service_state = yield self.add_service("wordpress")
1853
yield self.add_relation(
1854
"rel-type", "global", [service_state, "name", "role"])
1858
def callback(*args):
1859
results.append(True)
1861
yield service_state.watch_relation_states(callback)
1862
self.assertTrue(results)
1865
def test_watch_relations_when_being_created(self):
1867
We can watch relations before we have any.
1869
service_state = yield self.add_service("wordpress")
1871
wait_callback = [Deferred() for i in range(5)]
1874
def watch_relations(old_relations, new_relations):
1875
calls.append((old_relations, new_relations))
1876
wait_callback[len(calls) - 1].callback(True)
1879
service_state.watch_relation_states(watch_relations)
1881
# Callback is still untouched
1882
self.assertEquals(calls, [])
1884
# add a service relation and wait for the callback
1885
relation_state = yield self.add_relation(
1886
"rel-type", "global", [service_state, "name", "role"])
1887
yield wait_callback[1]
1890
self.assertEquals(len(calls), 2)
1891
old_relations, new_relations = calls[1]
1892
self.assertFalse(old_relations)
1893
self.assertEquals(new_relations[0].relation_role, "role")
1895
# add a new relation with the service assigned to it.
1896
relation_state2 = yield self.add_relation(
1897
"rel-type2", "global", [service_state, "app", "server"])
1898
yield wait_callback[2]
1900
self.assertEquals(len(calls), 3)
1901
old_relations, new_relations = calls[2]
1902
self.assertEquals([r.internal_relation_id for r in old_relations],
1903
[relation_state.internal_id])
1904
self.assertEquals([r.internal_relation_id for r in new_relations],
1905
[relation_state.internal_id,
1906
relation_state2.internal_id])
1909
def test_watch_relations_may_defer(self):
1911
The watch relations callback may return a deferred so that
1912
it performs some its logic asynchronously. In this case, it must
1913
not be called a second time before its postponed logic is finished
1916
wait_callback = [Deferred() for i in range(5)]
1917
finish_callback = [Deferred() for i in range(5)]
1921
def watch_relations(old_relations, new_relations):
1922
calls.append((old_relations, new_relations))
1923
wait_callback[len(calls) - 1].callback(True)
1924
return finish_callback[len(calls) - 1]
1926
service_state = yield self.add_service("s-1")
1927
service_state.watch_relation_states(watch_relations)
1929
# Shouldn't have any callbacks yet.
1930
self.assertEquals(calls, [])
1932
# Assign to a relation.
1933
yield self.add_relation("rel-type", "global",
1934
(service_state, "name", "role"))
1936
# Hold off until callback is started.
1937
yield wait_callback[0]
1939
# Assign to another relation.
1940
yield self.add_relation("rel-type", "global",
1941
(service_state, "name2", "role"))
1943
# Give a chance for something bad to happen.
1944
yield self.sleep(0.3)
1946
# Ensure we still have a single call.
1947
self.assertEquals(len(calls), 1)
1949
# Allow the first call to be completed, and wait on the
1951
finish_callback[0].callback(None)
1952
yield wait_callback[1]
1953
finish_callback[1].callback(None)
1955
# We should have the second change now.
1956
self.assertEquals(len(calls), 2)
1959
def test_get_relation_endpoints_service_name(self):
1960
"""Test getting endpoints with descriptor ``<service name>``"""
1961
yield self.add_service_from_charm("wordpress")
1963
(yield self.service_state_manager.get_relation_endpoints(
1965
[RelationEndpoint("wordpress", "varnish", "cache", "client"),
1966
RelationEndpoint("wordpress", "mysql", "db", "client"),
1967
RelationEndpoint("wordpress", "http", "url", "server"),
1968
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
1969
yield self.add_service_from_charm("riak")
1971
(yield self.service_state_manager.get_relation_endpoints(
1973
[RelationEndpoint("riak", "http", "admin", "server"),
1974
RelationEndpoint("riak", "http", "endpoint", "server"),
1975
RelationEndpoint("riak", "riak", "ring", "peer"),
1976
RelationEndpoint("riak", "juju-info", "juju-info", "server")])
1979
def test_get_relation_endpoints_service_name_relation_name(self):
1980
"""Test getting endpoints with ``<service name:relation name>``"""
1981
yield self.add_service_from_charm("wordpress")
1983
(yield self.service_state_manager.get_relation_endpoints(
1985
[RelationEndpoint("wordpress", "http", "url", "server"),
1986
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
1988
(yield self.service_state_manager.get_relation_endpoints(
1990
[RelationEndpoint("wordpress", "mysql", "db", "client"),
1991
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
1993
(yield self.service_state_manager.get_relation_endpoints(
1994
"wordpress:cache")),
1995
[RelationEndpoint("wordpress", "varnish", "cache", "client"),
1996
RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
1997
yield self.add_service_from_charm("riak")
1999
(yield self.service_state_manager.get_relation_endpoints(
2001
[RelationEndpoint("riak", "riak", "ring", "peer"),
2002
RelationEndpoint("riak", "juju-info", "juju-info", "server")])
2005
def test_descriptor_for_services_without_charms(self):
2006
"""Test with services that have no corresponding charms defined"""
2007
yield self.add_service("nocharm")
2008
# verify we get the implicit interface
2010
(yield self.service_state_manager.get_relation_endpoints(
2012
[RelationEndpoint("nocharm",
2013
"juju-info", "juju-info", "server")])
2016
(yield self.service_state_manager.get_relation_endpoints(
2017
"nocharm:nonsense")),
2018
[RelationEndpoint("nocharm",
2019
"juju-info", "juju-info", "server")])
2022
def test_descriptor_for_missing_service(self):
2023
"""Test with a service that is not in the topology"""
2024
yield self.assertFailure(
2025
self.service_state_manager.get_relation_endpoints("notadded"),
2026
ServiceStateNotFound)
2029
def test_bad_descriptors(self):
2030
"""Test that the descriptors meet the minimum naming standards"""
2031
yield self.assertFailure(
2032
self.service_state_manager.get_relation_endpoints("a:b:c"),
2034
yield self.assertFailure(
2035
self.service_state_manager.get_relation_endpoints(""),
2039
def test_join_descriptors_service_name(self):
2040
"""Test descriptor of the form ``<service name>`"""
2041
yield self.add_service_from_charm("wordpress")
2042
yield self.add_service_from_charm("mysql")
2044
(yield self.service_state_manager.join_descriptors(
2045
"wordpress", "mysql")),
2046
[(RelationEndpoint("wordpress", "mysql", "db", "client"),
2047
RelationEndpoint("mysql", "mysql", "server", "server"))])
2048
# symmetric - note the pair has rotated
2050
(yield self.service_state_manager.join_descriptors(
2051
"mysql", "wordpress")),
2052
[(RelationEndpoint("mysql", "mysql", "server", "server"),
2053
RelationEndpoint("wordpress", "mysql", "db", "client"))])
2054
yield self.add_service_from_charm("varnish")
2056
(yield self.service_state_manager.join_descriptors(
2057
"wordpress", "varnish")),
2058
[(RelationEndpoint("wordpress", "varnish", "cache", "client"),
2059
RelationEndpoint("varnish", "varnish", "webcache", "server"))])
2062
def test_join_descriptors_service_name_relation_name(self):
2063
"""Test joining descriptors ``<service name:relation name>``"""
2064
yield self.add_service_from_charm("wordpress")
2065
yield self.add_service_from_charm("mysql")
2067
(yield self.service_state_manager.join_descriptors(
2068
"wordpress:db", "mysql")),
2069
[(RelationEndpoint("wordpress", "mysql", "db", "client"),
2070
RelationEndpoint("mysql", "mysql", "server", "server"))])
2072
(yield self.service_state_manager.join_descriptors(
2073
"mysql:server", "wordpress")),
2074
[(RelationEndpoint("mysql", "mysql", "server", "server"),
2075
RelationEndpoint("wordpress", "mysql", "db", "client"))])
2077
(yield self.service_state_manager.join_descriptors(
2078
"mysql:server", "wordpress:db")),
2079
[(RelationEndpoint("mysql", "mysql", "server", "server"),
2080
RelationEndpoint("wordpress", "mysql", "db", "client"))])
2082
yield self.add_service_from_charm("varnish")
2084
(yield self.service_state_manager.join_descriptors(
2085
"wordpress:cache", "varnish")),
2086
[(RelationEndpoint("wordpress", "varnish", "cache", "client"),
2087
RelationEndpoint("varnish", "varnish", "webcache", "server"))])
2089
(yield self.service_state_manager.join_descriptors(
2090
"wordpress:cache", "varnish:webcache")),
2091
[(RelationEndpoint("wordpress", "varnish", "cache", "client"),
2092
RelationEndpoint("varnish", "varnish", "webcache", "server"))])
2095
def test_join_peer_descriptors(self):
2096
"""Test joining of peer relation descriptors"""
2097
yield self.add_service_from_charm("riak")
2099
(yield self.service_state_manager.join_descriptors(
2101
[(RelationEndpoint("riak", "riak", "ring", "peer"),
2102
RelationEndpoint("riak", "riak", "ring", "peer"))])
2104
(yield self.service_state_manager.join_descriptors(
2105
"riak:ring", "riak")),
2106
[(RelationEndpoint("riak", "riak", "ring", "peer"),
2107
RelationEndpoint("riak", "riak", "ring", "peer"))])
2109
(yield self.service_state_manager.join_descriptors(
2110
"riak:ring", "riak:ring")),
2111
[(RelationEndpoint("riak", "riak", "ring", "peer"),
2112
RelationEndpoint("riak", "riak", "ring", "peer"))])
2114
(yield self.service_state_manager.join_descriptors(
2115
"riak:no-ring", "riak:ring")),
2119
def test_join_descriptors_no_common_relation(self):
2120
"""Test joining of descriptors that do not share a relation"""
2121
yield self.add_service_from_charm("mysql")
2122
yield self.add_service_from_charm("riak")
2123
yield self.add_service_from_charm("wordpress")
2124
yield self.add_service_from_charm("varnish")
2125
self.assertEqual((yield self.service_state_manager.join_descriptors(
2126
"mysql", "riak")), [])
2127
self.assertEqual((yield self.service_state_manager.join_descriptors(
2128
"mysql:server", "riak:ring")), [])
2129
self.assertEqual((yield self.service_state_manager.join_descriptors(
2130
"varnish", "mysql")), [])
2131
self.assertEqual((yield self.service_state_manager.join_descriptors(
2132
"riak:ring", "riak:admin")), [])
2133
self.assertEqual((yield self.service_state_manager.join_descriptors(
2134
"riak", "wordpress")), [])
2137
def test_join_descriptors_no_service_state(self):
2138
"""Test joining of nonexistent services"""
2139
yield self.add_service_from_charm("wordpress")
2140
yield self.assertFailure(self.service_state_manager.join_descriptors(
2141
"wordpress", "nosuch"), ServiceStateNotFound)
2142
yield self.assertFailure(self.service_state_manager.join_descriptors(
2143
"notyet", "nosuch"), ServiceStateNotFound)
2146
def test_watch_services_initial_callback(self):
2147
"""Watch service processes initial state before returning.
2149
Note the callback is only executed if there is some meaningful state
2154
def callback(*args):
2155
results.append(True)
2157
yield self.service_state_manager.watch_service_states(callback)
2158
yield self.add_service("wordpress")
2159
yield self.poke_zk()
2160
self.assertTrue(results)
2163
def test_watch_services_when_being_created(self):
2165
It should be possible to start watching services even
2166
before they are created. In this case, the callback will
2167
be made when it's actually introduced.
2169
wait_callback = [Deferred() for i in range(10)]
2173
def watch_services(old_services, new_services):
2174
calls.append((old_services, new_services))
2175
wait_callback[len(calls) - 1].callback(True)
2178
self.service_state_manager.watch_service_states(watch_services)
2180
# Callback is still untouched.
2181
self.assertEquals(calls, [])
2183
# Add a service, and wait for callback.
2184
yield self.add_service("wordpress")
2185
yield wait_callback[0]
2187
# The first callback must have been fired, and it must have None
2188
# as the first argument because that's the first service seen.
2189
self.assertEquals(len(calls), 1)
2190
old_services, new_services = calls[0]
2191
self.assertEquals(old_services, set())
2192
self.assertEquals(new_services, set(["wordpress"]))
2194
# Add a service again.
2195
yield self.add_service("mysql")
2196
yield wait_callback[1]
2198
# Now the watch callback must have been fired with two
2199
# different service sets. The old one, and the new one.
2200
self.assertEquals(len(calls), 2)
2201
old_services, new_services = calls[1]
2202
self.assertEquals(old_services, set(["wordpress"]))
2203
self.assertEquals(new_services, set(["mysql", "wordpress"]))
2206
def test_watch_services_may_defer(self):
2208
The watch services callback may return a deferred so that it
2209
performs some of its logic asynchronously. In this case, it
2210
must not be called a second time before its postponed logic
2211
is finished completely.
2213
wait_callback = [Deferred() for i in range(10)]
2214
finish_callback = [Deferred() for i in range(10)]
2218
def watch_services(old_services, new_services):
2219
calls.append((old_services, new_services))
2220
wait_callback[len(calls) - 1].callback(True)
2221
return finish_callback[len(calls) - 1]
2224
self.service_state_manager.watch_service_states(watch_services)
2226
# Create the service.
2227
yield self.add_service("wordpress")
2229
# Hold off until callback is started.
2230
yield wait_callback[0]
2232
# Add another service.
2233
yield self.add_service("mysql")
2235
# Ensure we still have a single call.
2236
self.assertEquals(len(calls), 1)
2238
# Allow the first call to be completed, and wait on the
2240
finish_callback[0].callback(None)
2241
yield wait_callback[1]
2242
finish_callback[1].callback(None)
2244
# We should have the second change now.
2245
self.assertEquals(len(calls), 2)
2246
old_services, new_services = calls[1]
2247
self.assertEquals(old_services, set(["wordpress"]))
2248
self.assertEquals(new_services, set(["mysql", "wordpress"]))
2251
def test_watch_services_with_changing_topology(self):
2253
If the topology changes in an unrelated way, the services
2254
watch callback should not be called with two equal
2257
wait_callback = [Deferred() for i in range(10)]
2261
def watch_services(old_services, new_services):
2262
calls.append((old_services, new_services))
2263
wait_callback[len(calls) - 1].callback(True)
2266
self.service_state_manager.watch_service_states(watch_services)
2268
# Callback is still untouched.
2269
self.assertEquals(calls, [])
2271
# Add a service, and wait for callback.
2272
yield self.add_service("wordpress")
2273
yield wait_callback[0]
2275
# Now change the topology in an unrelated way.
2276
yield self.machine_state_manager.add_machine_state(
2279
# Add a service again.
2280
yield self.add_service("mysql")
2281
yield wait_callback[1]
2283
# But it *shouldn't* have happened.
2284
self.assertEquals(len(calls), 2)
2287
def test_watch_service_units_initial_callback(self):
2288
"""Watch service unit processes initial state before returning.
2290
Note the callback is only executed if there is some meaningful state
2295
def callback(*args):
2296
results.append(True)
2298
service_state = yield self.add_service("wordpress")
2299
yield service_state.watch_service_unit_states(callback)
2300
yield service_state.add_unit_state()
2301
yield self.poke_zk()
2302
self.assertTrue(results)
2305
def test_watch_service_units_when_being_created(self):
2307
It should be possible to start watching service units even
2308
before they are created. In this case, the callback will be
2309
made when it's actually introduced.
2311
wait_callback = [Deferred() for i in range(10)]
2315
def watch_service_units(old_service_units, new_service_units):
2316
calls.append((old_service_units, new_service_units))
2317
wait_callback[len(calls) - 1].callback(True)
2320
service_state = yield self.add_service("wordpress")
2321
service_state.watch_service_unit_states(watch_service_units)
2323
# Callback is still untouched.
2324
self.assertEquals(calls, [])
2326
# Add a service unit, and wait for callback.
2327
yield service_state.add_unit_state()
2329
yield wait_callback[0]
2331
# The first callback must have been fired, and it must have None
2332
# as the first argument because that's the first service seen.
2333
self.assertEquals(len(calls), 1)
2334
old_service_units, new_service_units = calls[0]
2335
self.assertEquals(old_service_units, set())
2336
self.assertEquals(new_service_units, set(["wordpress/0"]))
2338
# Add another service unit.
2339
yield service_state.add_unit_state()
2340
yield wait_callback[1]
2342
# Now the watch callback must have been fired with two
2343
# different service sets. The old one, and the new one.
2344
self.assertEquals(len(calls), 2)
2345
old_service_units, new_service_units = calls[1]
2346
self.assertEquals(old_service_units, set(["wordpress/0"]))
2347
self.assertEquals(new_service_units, set(["wordpress/0",
2351
def test_watch_service_units_may_defer(self):
2353
The watch service units callback may return a deferred so that
2354
it performs some of its logic asynchronously. In this case,
2355
it must not be called a second time before its postponed logic
2356
is finished completely.
2358
wait_callback = [Deferred() for i in range(10)]
2359
finish_callback = [Deferred() for i in range(10)]
2363
def watch_service_units(old_service_units, new_service_units):
2364
calls.append((old_service_units, new_service_units))
2365
wait_callback[len(calls) - 1].callback(True)
2366
return finish_callback[len(calls) - 1]
2369
service_state = yield self.add_service("wordpress")
2370
service_state.watch_service_unit_states(watch_service_units)
2372
# Create the service unit.
2373
yield service_state.add_unit_state()
2375
# Hold off until callback is started.
2376
yield wait_callback[0]
2378
# Add another service unit.
2379
yield service_state.add_unit_state()
2381
# Ensure we still have a single call.
2382
self.assertEquals(len(calls), 1)
2384
# Allow the first call to be completed, and wait on the
2386
finish_callback[0].callback(None)
2387
yield wait_callback[1]
2388
finish_callback[1].callback(None)
2390
# We should have the second change now.
2391
self.assertEquals(len(calls), 2)
2392
old_service_units, new_service_units = calls[1]
2393
self.assertEquals(old_service_units, set(["wordpress/0"]))
2395
new_service_units, set(["wordpress/0", "wordpress/1"]))
2398
def test_watch_service_units_with_changing_topology(self):
2400
If the topology changes in an unrelated way, the services
2401
watch callback should not be called with two equal
2404
wait_callback = [Deferred() for i in range(10)]
2408
def watch_service_units(old_service_units, new_service_units):
2409
calls.append((old_service_units, new_service_units))
2410
wait_callback[len(calls) - 1].callback(True)
2413
service_state = yield self.add_service("wordpress")
2414
service_state.watch_service_unit_states(watch_service_units)
2416
# Callback is still untouched.
2417
self.assertEquals(calls, [])
2419
# Add a service, and wait for callback.
2420
yield service_state.add_unit_state()
2421
yield wait_callback[0]
2423
# Now change the topology in an unrelated way.
2424
yield self.machine_state_manager.add_machine_state(
2427
# Add a service again.
2428
yield service_state.add_unit_state()
2429
yield wait_callback[1]
2431
# But it *shouldn't* have happened.
2432
self.assertEquals(len(calls), 2)
2435
def test_service_config_get_set(self):
2436
"""Validate that we can set and get service config options."""
2437
wordpress = yield self.add_service_from_charm("wordpress")
2439
# attempt to get the initialized service state
2440
config = yield wordpress.get_config()
2442
# the initial state is empty
2443
self.assertEqual(config, {"blog-title": "My Title"})
2445
# behaves as a normal dict
2446
self.assertRaises(KeyError, config.__getitem__, "missing")
2448
# various ways to set state
2449
config.update(dict(alpha="beta", one="two"))
2450
config["another"] = "value"
2453
yield config.write()
2455
# we should be able to read the config and see the same values
2456
# (in this case it would be the cached object)
2457
config2 = yield wordpress.get_config()
2458
self.assertEqual(config2, {"alpha": "beta",
2461
"blog-title": "My Title"})
2463
# now set a non-string value and recover it
2464
config2["number"] = 1
2465
config2["one"] = None
2466
yield config2.write()
2469
self.assertEquals(config["number"], 1)
2470
self.assertEquals(config["one"], None)
2473
def test_service_config_get_returns_new(self):
2474
"""Validate that we can set and get service config options."""
2475
wordpress = yield self.add_service_from_charm("wordpress")
2477
# attempt to get the initialized service state
2478
config = yield wordpress.get_config()
2479
config.update({"foo": "bar"})
2480
# Defaults come through
2481
self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"})
2483
config2 = yield wordpress.get_config()
2484
self.assertEqual(config2, {"blog-title": "My Title"})
2486
yield config.write()
2487
self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"})
2489
# Config2 is still empty (a different YAML State), with charm defaults.
2490
self.assertEqual(config2, {"blog-title": "My Title"})
2492
yield config2.read()
2493
self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"})
2495
# The default was never written to storage.
2496
data, stat = yield self.client.get(
2497
"/services/%s/config" % wordpress.internal_id)
2498
self.assertEqual(yaml.load(data), {"foo": "bar"})
2501
def test_get_charm_state(self):
2502
wordpress = yield self.add_service_from_charm("wordpress")
2503
charm = yield wordpress.get_charm_state()
2505
self.assertEqual(charm.name, "wordpress")
2506
metadata = yield charm.get_metadata()
2507
self.assertEqual(metadata.summary, "Blog engine")
2530
2510
class ExposedFlagTest(ServiceStateManagerTestBase):
2533
def test_set_and_clear_exposed_flag(self):
2534
"""An exposed flag can be set on a service."""
2537
service_state = yield self.service_state_manager.add_service_state(
2538
"wordpress", self.charm_state, dummy_constraints)
2539
exposed_flag = yield service_state.get_exposed_flag()
2540
self.assertEqual(exposed_flag, False)
2543
yield service_state.set_exposed_flag()
2544
exposed_flag = yield service_state.get_exposed_flag()
2545
self.assertEqual(exposed_flag, True)
2547
# Can be set multiple times
2548
yield service_state.set_exposed_flag()
2549
exposed_flag = yield service_state.get_exposed_flag()
2550
self.assertEqual(exposed_flag, True)
2553
yield service_state.clear_exposed_flag()
2554
exposed_flag = yield service_state.get_exposed_flag()
2555
self.assertEqual(exposed_flag, False)
2557
# Can be cleared multiple times
2558
yield service_state.clear_exposed_flag()
2559
exposed_flag = yield service_state.get_exposed_flag()
2560
self.assertEqual(exposed_flag, False)
2563
def test_watch_exposed_flag(self):
2564
"""An exposed watch is setup on a permanent basis."""
2565
service_state = yield self.add_service("wordpress")
2569
def callback(value):
2570
results.append(value)
2572
yield service_state.set_exposed_flag()
2574
# verify that the current state is processed before returning
2575
yield service_state.watch_exposed_flag(callback)
2576
yield service_state.clear_exposed_flag()
2577
yield service_state.set_exposed_flag()
2578
yield service_state.clear_exposed_flag()
2579
yield service_state.clear_exposed_flag() # should be ignored
2580
yield service_state.set_exposed_flag()
2583
(yield service_state.get_exposed_flag()),
2585
self.assertEqual(results, [True, False, True, False, True])
2588
def test_stop_watch_exposed_flag(self):
2589
"""The watch is setup on a permanent basis, but can be stopped.
2591
The callback can raise StopWatcher at any time to stop the
2594
service_state = yield self.add_service("wordpress")
2597
def callback(value):
2598
results.append(value)
2599
if len(results) == 2:
2601
if len(results) == 4:
2604
yield service_state.watch_exposed_flag(callback)
2605
yield service_state.set_exposed_flag()
2606
# two sets in a row do not retrigger callback since no change
2608
yield service_state.set_exposed_flag()
2609
yield service_state.clear_exposed_flag()
2611
# no callback now, because StopWatcher was just raised
2612
yield service_state.set_exposed_flag()
2614
# then setup watch again
2615
yield service_state.watch_exposed_flag(callback)
2616
yield service_state.clear_exposed_flag()
2618
# no callbacks for these two lines, because StopWatcher was
2620
yield service_state.set_exposed_flag()
2621
yield service_state.clear_exposed_flag()
2624
(yield service_state.get_exposed_flag()), False)
2625
self.assertEqual(results, [False, True, True, False])
2628
def test_watch_exposed_flag_waits_on_slow_callbacks(self):
2629
"""Verify that a slow watch callback is still invoked serially."""
2631
service_state = yield self.add_service("wordpress")
2633
callbacks = [Deferred() for i in range(3)]
2634
before = [] # values seen before callback in `cb_watch`
2635
after = [] # and after
2638
def cb_watch(value):
2639
before.append(value)
2640
yield callbacks[len(before) - 1]
2641
after.append((yield service_state.get_exposed_flag()))
2643
yield service_state.set_exposed_flag()
2645
# Need to let first callback be completed, otherwise will wait
2646
# forever in watch_exposed_flag. This is because `cb_watch` is
2647
# initially called in the setup of the watch
2648
callbacks[0].callback(True)
2650
yield service_state.watch_exposed_flag(cb_watch)
2651
self.assertEqual(before, [True])
2652
self.assertEqual(after, [True])
2654
# Go through the watch again, verifying that it is waiting on
2656
yield service_state.clear_exposed_flag()
2657
yield self.poke_zk()
2658
self.assertEqual(before, [True, False])
2659
self.assertEqual(after, [True])
2661
# Now let `cb_watch` finish
2662
callbacks[1].callback(True)
2663
yield self.poke_zk()
2665
# Go through another watch cycle
2666
yield service_state.set_exposed_flag()
2667
yield self.poke_zk()
2669
# Verify results, still haven't advanced through `callbacks[2]`
2670
self.assertEqual(before, [True, False, True])
2671
self.assertEqual(after, [True, False])
2673
# Now let it go through, verifying that `before` hasn't
2674
# changed, but `after` has now updated
2675
callbacks[2].callback(True)
2676
yield self.poke_zk()
2677
self.assertEqual(before, [True, False, True])
2678
self.assertEqual(after, [True, False, True])
2513
def test_set_and_clear_exposed_flag(self):
2514
"""An exposed flag can be set on a service."""
2517
service_state = yield self.service_state_manager.add_service_state(
2518
"wordpress", self.charm_state, dummy_constraints)
2519
exposed_flag = yield service_state.get_exposed_flag()
2520
self.assertEqual(exposed_flag, False)
2523
yield service_state.set_exposed_flag()
2524
exposed_flag = yield service_state.get_exposed_flag()
2525
self.assertEqual(exposed_flag, True)
2527
# Can be set multiple times
2528
yield service_state.set_exposed_flag()
2529
exposed_flag = yield service_state.get_exposed_flag()
2530
self.assertEqual(exposed_flag, True)
2533
yield service_state.clear_exposed_flag()
2534
exposed_flag = yield service_state.get_exposed_flag()
2535
self.assertEqual(exposed_flag, False)
2537
# Can be cleared multiple times
2538
yield service_state.clear_exposed_flag()
2539
exposed_flag = yield service_state.get_exposed_flag()
2540
self.assertEqual(exposed_flag, False)
2543
def test_watch_exposed_flag(self):
2544
"""An exposed watch is setup on a permanent basis."""
2545
service_state = yield self.add_service("wordpress")
2549
def callback(value):
2550
results.append(value)
2552
yield service_state.set_exposed_flag()
2554
# verify that the current state is processed before returning
2555
yield service_state.watch_exposed_flag(callback)
2556
yield service_state.clear_exposed_flag()
2557
yield service_state.set_exposed_flag()
2558
yield service_state.clear_exposed_flag()
2559
yield service_state.clear_exposed_flag() # should be ignored
2560
yield service_state.set_exposed_flag()
2562
self.assertEqual((yield service_state.get_exposed_flag()),
2564
self.assertEqual(results, [True, False, True, False, True])
2567
def test_stop_watch_exposed_flag(self):
2568
"""The watch is setup on a permanent basis, but can be stopped.
2570
The callback can raise StopWatcher at any time to stop the
2573
service_state = yield self.add_service("wordpress")
2576
def callback(value):
2577
results.append(value)
2578
if len(results) == 2:
2580
if len(results) == 4:
2583
yield service_state.watch_exposed_flag(callback)
2584
yield service_state.set_exposed_flag()
2585
# two sets in a row do not retrigger callback since no change
2587
yield service_state.set_exposed_flag()
2588
yield service_state.clear_exposed_flag()
2590
# no callback now, because StopWatcher was just raised
2591
yield service_state.set_exposed_flag()
2593
# then setup watch again
2594
yield service_state.watch_exposed_flag(callback)
2595
yield service_state.clear_exposed_flag()
2597
# no callbacks for these two lines, because StopWatcher was
2599
yield service_state.set_exposed_flag()
2600
yield service_state.clear_exposed_flag()
2603
(yield service_state.get_exposed_flag()), False)
2604
self.assertEqual(results, [False, True, True, False])
2607
def test_watch_exposed_flag_waits_on_slow_callbacks(self):
2608
"""Verify that a slow watch callback is still invoked serially."""
2610
service_state = yield self.add_service("wordpress")
2612
callbacks = [Deferred() for i in range(3)]
2613
before = [] # values seen before callback in `cb_watch`
2614
after = [] # and after
2617
def cb_watch(value):
2618
before.append(value)
2619
yield callbacks[len(before) - 1]
2620
after.append((yield service_state.get_exposed_flag()))
2622
yield service_state.set_exposed_flag()
2624
# Need to let first callback be completed, otherwise will wait
2625
# forever in watch_exposed_flag. This is because `cb_watch` is
2626
# initially called in the setup of the watch
2627
callbacks[0].callback(True)
2629
yield service_state.watch_exposed_flag(cb_watch)
2630
self.assertEqual(before, [True])
2631
self.assertEqual(after, [True])
2633
# Go through the watch again, verifying that it is waiting on
2635
yield service_state.clear_exposed_flag()
2636
yield self.poke_zk()
2637
self.assertEqual(before, [True, False])
2638
self.assertEqual(after, [True])
2640
# Now let `cb_watch` finish
2641
callbacks[1].callback(True)
2642
yield self.poke_zk()
2644
# Go through another watch cycle
2645
yield service_state.set_exposed_flag()
2646
yield self.poke_zk()
2648
# Verify results, still haven't advanced through `callbacks[2]`
2649
self.assertEqual(before, [True, False, True])
2650
self.assertEqual(after, [True, False])
2652
# Now let it go through, verifying that `before` hasn't
2653
# changed, but `after` has now updated
2654
callbacks[2].callback(True)
2655
yield self.poke_zk()
2656
self.assertEqual(before, [True, False, True])
2657
self.assertEqual(after, [True, False, True])
2681
2660
class PortsTest(ServiceStateManagerTestBase):
2684
def test_watch_config_options(self):
2685
"""Verify callback trigger on config options modification"""
2687
service_state = yield self.service_state_manager.add_service_state(
2688
"wordpress", self.charm_state, dummy_constraints)
2691
def callback(value):
2692
results.append(value)
2694
yield service_state.watch_config_state(callback)
2695
config = yield service_state.get_config()
2696
config["alpha"] = "beta"
2697
yield config.write()
2699
yield self.poke_zk()
2700
self.assertIdentical(results.pop(0), True)
2701
self.assertIdentical(results.pop(0).type_name, "changed")
2703
# and changing it again should trigger the callback again
2704
config["gamma"] = "delta"
2705
yield config.write()
2707
yield self.poke_zk()
2708
self.assertEqual(len(results), 1)
2709
self.assertIdentical(results.pop(0).type_name, "changed")
2712
def test_get_open_ports(self):
2713
"""Verify introspection and that the ports changes are immediate."""
2714
service_state = yield self.add_service("wordpress")
2715
unit_state = yield service_state.add_unit_state()
2717
# verify no open ports before activity
2718
self.assertEqual((yield unit_state.get_open_ports()), [])
2720
# then open_port, close_port
2721
yield unit_state.open_port(80, "tcp")
2723
(yield unit_state.get_open_ports()),
2724
[{"port": 80, "proto": "tcp"}])
2726
yield unit_state.open_port(53, "udp")
2728
(yield unit_state.get_open_ports()),
2729
[{"port": 80, "proto": "tcp"},
2730
{"port": 53, "proto": "udp"}])
2732
yield unit_state.open_port(53, "tcp")
2734
(yield unit_state.get_open_ports()),
2735
[{"port": 80, "proto": "tcp"},
2736
{"port": 53, "proto": "udp"},
2737
{"port": 53, "proto": "tcp"}])
2739
yield unit_state.open_port(443, "tcp")
2741
(yield unit_state.get_open_ports()),
2742
[{"port": 80, "proto": "tcp"},
2743
{"port": 53, "proto": "udp"},
2744
{"port": 53, "proto": "tcp"},
2745
{"port": 443, "proto": "tcp"}])
2747
yield unit_state.close_port(80, "tcp")
2749
(yield unit_state.get_open_ports()),
2750
[{"port": 53, "proto": "udp"},
2751
{"port": 53, "proto": "tcp"},
2752
{"port": 443, "proto": "tcp"}])
2755
def test_close_open_port(self):
2756
"""Verify closing an unopened port, then actually opening it, works."""
2757
service_state = yield self.add_service("wordpress")
2758
unit_state = yield service_state.add_unit_state()
2759
unit_name = unit_state.unit_name
2761
yield unit_state.close_port(80, "tcp")
2763
(yield unit_state.get_open_ports()),
2766
yield unit_state.open_port(80, "tcp")
2768
(yield unit_state.get_open_ports()),
2769
[{"port": 80, "proto": "tcp"}])
2772
def test_open_ports_znode_representation(self):
2773
"""Verify the specific representation of open ports in ZK."""
2774
service_state = yield self.add_service("wordpress")
2775
unit_state = yield service_state.add_unit_state()
2776
ports_path = "/units/unit-0000000000/ports"
2778
# verify no node exists before activity
2780
self.client.get(ports_path), zookeeper.NoNodeException)
2782
# verify representation format after open_port, close_port
2783
yield unit_state.open_port(80, "tcp")
2784
content, stat = yield self.client.get(ports_path)
2787
{"open": [{"port": 80, "proto": "tcp"}]})
2789
yield unit_state.open_port(53, "udp")
2790
content, stat = yield self.client.get(ports_path)
2793
{"open": [{"port": 80, "proto": "tcp"},
2794
{"port": 53, "proto": "udp"}]})
2796
yield unit_state.open_port(443, "tcp")
2797
content, stat = yield self.client.get(ports_path)
2800
{"open": [{"port": 80, "proto": "tcp"},
2801
{"port": 53, "proto": "udp"},
2802
{"port": 443, "proto": "tcp"}]})
2804
yield unit_state.close_port(80, "tcp")
2805
content, stat = yield self.client.get(ports_path)
2808
{"open": [{"port": 53, "proto": "udp"},
2809
{"port": 443, "proto": "tcp"}]})
2812
def test_watch_ports(self):
2813
"""An open ports watch notifies of presence and changes."""
2814
service_state = yield self.add_service("wordpress")
2815
unit_state = yield service_state.add_unit_state()
2816
yield unit_state.open_port(80, "tcp")
2817
yield unit_state.open_port(53, "udp")
2818
yield unit_state.open_port(443, "tcp")
2822
def callback(value):
2823
results.append(value)
2825
# set up a one-time watch
2826
unit_state.watch_ports(callback)
2829
yield unit_state.close_port(80, "tcp")
2830
yield unit_state.open_port(22, "tcp")
2832
# but see just the callback with one changed event, plus initial setup
2833
yield self.poke_zk()
2834
self.assertEqual(len(results), 3)
2835
self.assertEqual(results.pop(0), True)
2836
self.assertEqual(results.pop().type_name, "changed")
2837
self.assertEqual(results.pop().type_name, "changed")
2839
(yield unit_state.get_open_ports()),
2840
[{'port': 53, 'proto': 'udp'},
2841
{'port': 443, 'proto': 'tcp'},
2842
{'port': 22, 'proto': 'tcp'}])
2845
def test_stop_watch_ports(self):
2846
"""An exposed watch can be instituted on a permanent basis.
2848
However the callback can raise StopWatcher any time to stop the watch.
2850
service_state = yield self.add_service("wordpress")
2851
unit_state = yield service_state.add_unit_state()
2852
yield unit_state.open_port(80, "tcp")
2856
def callback(value):
2857
results.append(value)
2858
if len(results) == 1:
2860
if len(results) == 4:
2863
unit_state.watch_ports(callback)
2864
yield unit_state.close_port(80, "tcp")
2865
yield self.poke_zk()
2866
self.assertEqual(len(results), 1)
2867
self.assertEqual(results[0], True)
2869
unit_state.watch_ports(callback)
2870
yield unit_state.open_port(53, "udp")
2871
yield self.poke_zk()
2872
yield self.sleep(0.1)
2873
self.assertEqual(len(results), 3)
2874
self.assertEqual(results[0], True)
2875
self.assertEqual(results[1], True)
2876
self.assertEqual(results[2].type_name, "changed")
2878
(yield unit_state.get_open_ports()),
2879
[{'port': 53, 'proto': 'udp'}])
2882
def test_watch_ports_slow_callbacks(self):
2883
"""A slow watch callback is still invoked serially."""
2884
unit_state = yield self.get_unit_state()
2886
callbacks = [Deferred() for i in range(5)]
2892
results.append(value)
2893
yield callbacks[len(results) - 1]
2894
contents.append((yield unit_state.get_open_ports()))
2896
callbacks[0].callback(True)
2897
yield unit_state.watch_ports(watch)
2899
# These get collapsed into a single event
2900
yield unit_state.open_port(80, "tcp")
2901
yield unit_state.open_port(53, "udp")
2902
yield unit_state.open_port(443, "tcp")
2903
yield unit_state.close_port(80, "tcp")
2904
yield self.poke_zk()
2906
# Verify the callback hasn't completed
2907
self.assertEqual(len(results), 2)
2908
self.assertEqual(len(contents), 1)
2911
callbacks[1].callback(True)
2912
yield self.poke_zk()
2914
# Verify the callback hasn't completed
2915
self.assertEqual(len(contents), 2)
2917
callbacks[2].callback(True)
2918
yield self.poke_zk()
2921
self.assertEqual(len(contents), 3)
2922
self.assertEqual(results[-1].type_name, "changed")
2923
self.assertEqual(contents[-1], [
2924
{'port': 53, 'proto': 'udp'}, {'port': 443, 'proto': 'tcp'}])
2925
yield self.poke_zk()
2927
def test_parse_service_name(self):
2928
self.assertEqual(parse_service_name("wordpress/0"), "wordpress")
2929
self.assertEqual(parse_service_name("myblog/1"), "myblog")
2930
self.assertRaises(ValueError, parse_service_name, "invalid")
2931
self.assertRaises(ValueError, parse_service_name, None)
2663
def test_watch_config_options(self):
2664
"""Verify callback trigger on config options modification"""
2666
service_state = yield self.service_state_manager.add_service_state(
2667
"wordpress", self.charm_state, dummy_constraints)
2670
def callback(value):
2671
results.append(value)
2673
yield service_state.watch_config_state(callback)
2674
config = yield service_state.get_config()
2675
config["alpha"] = "beta"
2676
yield config.write()
2678
yield self.poke_zk()
2679
self.assertIdentical(results.pop(0), True)
2680
self.assertIdentical(results.pop(0).type_name, "changed")
2682
# and changing it again should trigger the callback again
2683
config["gamma"] = "delta"
2684
yield config.write()
2686
yield self.poke_zk()
2687
self.assertEqual(len(results), 1)
2688
self.assertIdentical(results.pop(0).type_name, "changed")
2691
def test_get_open_ports(self):
2692
"""Verify introspection and that the ports changes are immediate."""
2693
service_state = yield self.add_service("wordpress")
2694
unit_state = yield service_state.add_unit_state()
2696
# verify no open ports before activity
2697
self.assertEqual((yield unit_state.get_open_ports()), [])
2699
# then open_port, close_port
2700
yield unit_state.open_port(80, "tcp")
2702
(yield unit_state.get_open_ports()),
2703
[{"port": 80, "proto": "tcp"}])
2705
yield unit_state.open_port(53, "udp")
2707
(yield unit_state.get_open_ports()),
2708
[{"port": 80, "proto": "tcp"},
2709
{"port": 53, "proto": "udp"}])
2711
yield unit_state.open_port(53, "tcp")
2713
(yield unit_state.get_open_ports()),
2714
[{"port": 80, "proto": "tcp"},
2715
{"port": 53, "proto": "udp"},
2716
{"port": 53, "proto": "tcp"}])
2718
yield unit_state.open_port(443, "tcp")
2720
(yield unit_state.get_open_ports()),
2721
[{"port": 80, "proto": "tcp"},
2722
{"port": 53, "proto": "udp"},
2723
{"port": 53, "proto": "tcp"},
2724
{"port": 443, "proto": "tcp"}])
2726
yield unit_state.close_port(80, "tcp")
2728
(yield unit_state.get_open_ports()),
2729
[{"port": 53, "proto": "udp"},
2730
{"port": 53, "proto": "tcp"},
2731
{"port": 443, "proto": "tcp"}])
2734
def test_close_open_port(self):
2735
"""Verify closing an unopened port, then actually opening it, works."""
2736
service_state = yield self.add_service("wordpress")
2737
unit_state = yield service_state.add_unit_state()
2739
yield unit_state.close_port(80, "tcp")
2741
(yield unit_state.get_open_ports()),
2744
yield unit_state.open_port(80, "tcp")
2746
(yield unit_state.get_open_ports()),
2747
[{"port": 80, "proto": "tcp"}])
2750
def test_open_ports_znode_representation(self):
2751
"""Verify the specific representation of open ports in ZK."""
2752
service_state = yield self.add_service("wordpress")
2753
unit_state = yield service_state.add_unit_state()
2754
ports_path = "/units/unit-0000000000/ports"
2756
# verify no node exists before activity
2758
self.client.get(ports_path), zookeeper.NoNodeException)
2760
# verify representation format after open_port, close_port
2761
yield unit_state.open_port(80, "tcp")
2762
content, stat = yield self.client.get(ports_path)
2765
{"open": [{"port": 80, "proto": "tcp"}]})
2767
yield unit_state.open_port(53, "udp")
2768
content, stat = yield self.client.get(ports_path)
2771
{"open": [{"port": 80, "proto": "tcp"},
2772
{"port": 53, "proto": "udp"}]})
2774
yield unit_state.open_port(443, "tcp")
2775
content, stat = yield self.client.get(ports_path)
2778
{"open": [{"port": 80, "proto": "tcp"},
2779
{"port": 53, "proto": "udp"},
2780
{"port": 443, "proto": "tcp"}]})
2782
yield unit_state.close_port(80, "tcp")
2783
content, stat = yield self.client.get(ports_path)
2786
{"open": [{"port": 53, "proto": "udp"},
2787
{"port": 443, "proto": "tcp"}]})
2790
def test_watch_ports(self):
2791
"""An open ports watch notifies of presence and changes."""
2792
service_state = yield self.add_service("wordpress")
2793
unit_state = yield service_state.add_unit_state()
2794
yield unit_state.open_port(80, "tcp")
2795
yield unit_state.open_port(53, "udp")
2796
yield unit_state.open_port(443, "tcp")
2800
def callback(value):
2801
results.append(value)
2803
# set up a one-time watch
2804
unit_state.watch_ports(callback)
2807
yield unit_state.close_port(80, "tcp")
2808
yield unit_state.open_port(22, "tcp")
2810
# but see just the callback with one changed event, plus initial setup
2811
yield self.poke_zk()
2812
self.assertEqual(len(results), 3)
2813
self.assertEqual(results.pop(0), True)
2814
self.assertEqual(results.pop().type_name, "changed")
2815
self.assertEqual(results.pop().type_name, "changed")
2817
(yield unit_state.get_open_ports()),
2818
[{"port": 53, "proto": "udp"},
2819
{"port": 443, "proto": "tcp"},
2820
{"port": 22, "proto": "tcp"}])
2823
def test_stop_watch_ports(self):
2824
"""An exposed watch can be instituted on a permanent basis.
2826
However the callback can raise StopWatcher any time to stop the watch.
2828
service_state = yield self.add_service("wordpress")
2829
unit_state = yield service_state.add_unit_state()
2830
yield unit_state.open_port(80, "tcp")
2834
def callback(value):
2835
results.append(value)
2836
if len(results) == 1:
2838
if len(results) == 4:
2841
unit_state.watch_ports(callback)
2842
yield unit_state.close_port(80, "tcp")
2843
yield self.poke_zk()
2844
self.assertEqual(len(results), 1)
2845
self.assertEqual(results[0], True)
2847
unit_state.watch_ports(callback)
2848
yield unit_state.open_port(53, "udp")
2849
yield self.poke_zk()
2850
yield self.sleep(0.1)
2851
self.assertEqual(len(results), 3)
2852
self.assertEqual(results[0], True)
2853
self.assertEqual(results[1], True)
2854
self.assertEqual(results[2].type_name, "changed")
2856
(yield unit_state.get_open_ports()),
2857
[{"port": 53, "proto": "udp"}])
2860
def test_watch_ports_slow_callbacks(self):
2861
"""A slow watch callback is still invoked serially."""
2862
unit_state = yield self.get_unit_state()
2864
callbacks = [Deferred() for i in range(5)]
2870
results.append(value)
2871
yield callbacks[len(results) - 1]
2872
contents.append((yield unit_state.get_open_ports()))
2874
callbacks[0].callback(True)
2875
yield unit_state.watch_ports(watch)
2877
# These get collapsed into a single event
2878
yield unit_state.open_port(80, "tcp")
2879
yield unit_state.open_port(53, "udp")
2880
yield unit_state.open_port(443, "tcp")
2881
yield unit_state.close_port(80, "tcp")
2882
yield self.poke_zk()
2884
# Verify the callback hasn't completed
2885
self.assertEqual(len(results), 2)
2886
self.assertEqual(len(contents), 1)
2889
callbacks[1].callback(True)
2890
yield self.poke_zk()
2892
# Verify the callback hasn't completed
2893
self.assertEqual(len(contents), 2)
2895
callbacks[2].callback(True)
2896
yield self.poke_zk()
2899
self.assertEqual(len(contents), 3)
2900
self.assertEqual(results[-1].type_name, "changed")
2901
self.assertEqual(contents[-1], [{"port": 53, "proto": "udp"},
2902
{"port": 443, "proto": "tcp"}])
2903
yield self.poke_zk()
2905
def test_parse_service_name(self):
2906
self.assertEqual(parse_service_name("wordpress/0"), "wordpress")
2907
self.assertEqual(parse_service_name("myblog/1"), "myblog")
2908
self.assertRaises(ValueError, parse_service_name, "invalid")
2909
self.assertRaises(ValueError, parse_service_name, None)