Merge lp:~bcsaller/pyjuju/subordinate-control-status into lp:pyjuju
- subordinate-control-status
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Kapil Thangavelu | ||||
Approved revision: | 523 | ||||
Merged at revision: | 515 | ||||
Proposed branch: | lp:~bcsaller/pyjuju/subordinate-control-status | ||||
Merge into: | lp:pyjuju | ||||
Diff against target: |
7069 lines (+3577/-3059) 12 files modified
juju/control/add_unit.py (+5/-0) juju/control/deploy.py (+8/-4) juju/control/remove_unit.py (+5/-0) juju/control/status.py (+423/-157) juju/control/tests/test_add_unit.py (+14/-0) juju/control/tests/test_deploy.py (+27/-0) juju/control/tests/test_remove_unit.py (+12/-0) juju/control/tests/test_status.py (+165/-44) juju/state/service.py (+7/-1) juju/state/tests/test_charm.py (+19/-3) juju/state/tests/test_service.py (+2891/-2850) juju/unit/tests/test_lifecycle.py (+1/-0) |
||||
To merge this branch: | bzr merge lp:~bcsaller/pyjuju/subordinate-control-status | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+100489@code.launchpad.net |
Commit message
Description of the change
juju/control should be aware of subordinates
Includes support for add-unit, deploy, status, remove-unit
Benjamin Saller (bcsaller) wrote : | # |
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
- 523. By Benjamin Saller
-
better comments
Kapil Thangavelu (hazmat) wrote : | # |
This looks good. The refactoring of status isn't complete though.. it
needs some more docs, this area of the codebase is frequently touched,
and it needs more introduction to what the responsibility of the various
methods are, esp. given their use of shared instance state variables in
a functional style.
https:/
File juju/control/
https:/
juju/control/
it shouldn't exist if its only purpose is for existing tests.. i'd just
update the tests to the new api.. that's part of the refactoring, else
its just dead code.
https:/
juju/control/
ServiceStateMan
these where documented in a self referential way post the diff, but
saying 'service_name' to 'state' isn't very clear, if its referring to
the actual state object (unlikely), or what structure the dictionary
termed state has.
https:/
juju/control/
rel_svc_map):
this should have some docs, what are the params
https:/
juju/control/
this method could use some documentation, what are the params
https:/
juju/control/
self.service_
please document the return value.
https:/
juju/control/
self.service_
please document what the method is doing..
ie. inspects all the relations of a service and...?
Kapil Thangavelu (hazmat) wrote : | # |
https:/
File juju/control/
https:/
juju/control/
self.build_
this method is a crutch, its creating way more stuff than what is
actually tested for. its better to simplify and model an api for
creation of whats needed, instead of relying on a megamethod to build
world every test.
i'd like to see a test here of removing a primary unit with a
subordinate attached, as that broke status on my previous usage of this
feature.
- 524. By Benjamin Saller
-
move status changes to proper branch, added docs, moved collect method to test module
Kapil Thangavelu (hazmat) wrote : | # |
Latest changes look good, time to merge.
- 525. By Benjamin Saller
-
wip
- 526. By Benjamin Saller
-
handle case where principal unit is removed
Preview Diff
1 | === modified file 'juju/control/add_unit.py' |
2 | --- juju/control/add_unit.py 2012-03-29 13:40:45 +0000 |
3 | +++ juju/control/add_unit.py 2012-04-05 21:24:20 +0000 |
4 | @@ -4,6 +4,7 @@ |
5 | |
6 | from juju.control import legacy |
7 | from juju.control.utils import get_environment, sync_environment_state |
8 | +from juju.errors import JujuError |
9 | from juju.state.placement import place_unit |
10 | from juju.state.service import ServiceStateManager |
11 | |
12 | @@ -51,6 +52,10 @@ |
13 | |
14 | service_manager = ServiceStateManager(client) |
15 | service_state = yield service_manager.get_service_state(service_name) |
16 | + if (yield service_state.is_subordinate()): |
17 | + raise JujuError("Subordinate services acquire units from " |
18 | + "their principal service.") |
19 | + |
20 | for i in range(num_units): |
21 | unit_state = yield service_state.add_unit_state() |
22 | yield place_unit(client, placement_policy, unit_state) |
23 | |
24 | === modified file 'juju/control/deploy.py' |
25 | --- juju/control/deploy.py 2012-03-29 13:40:45 +0000 |
26 | +++ juju/control/deploy.py 2012-04-05 21:24:20 +0000 |
27 | @@ -37,7 +37,7 @@ |
28 | sub_parser.add_argument( |
29 | "--repository", |
30 | help="Directory for charm lookup and retrieval", |
31 | - default=os.environ.get('JUJU_REPOSITORY'), |
32 | + default=os.environ.get("JUJU_REPOSITORY"), |
33 | type=expand_path) |
34 | |
35 | sub_parser.add_argument( |
36 | @@ -170,9 +170,13 @@ |
37 | yield state.write() |
38 | |
39 | # Create desired number of service units |
40 | - for i in xrange(num_units): |
41 | - unit_state = yield service_state.add_unit_state() |
42 | - yield place_unit(client, placement_policy, unit_state) |
43 | + if (yield service_state.is_subordinate()): |
44 | + log.info("Subordinate %r awaiting relationship " |
45 | + "to principal for deployment.", service_name) |
46 | + else: |
47 | + for i in xrange(num_units): |
48 | + unit_state = yield service_state.add_unit_state() |
49 | + yield place_unit(client, placement_policy, unit_state) |
50 | |
51 | # Check if we have any peer relations to establish |
52 | if charm.metadata.peers: |
53 | |
54 | === modified file 'juju/control/remove_unit.py' |
55 | --- juju/control/remove_unit.py 2011-11-29 22:50:38 +0000 |
56 | +++ juju/control/remove_unit.py 2012-04-05 21:24:20 +0000 |
57 | @@ -2,6 +2,7 @@ |
58 | |
59 | from twisted.internet.defer import inlineCallbacks |
60 | |
61 | +from juju.errors import JujuError |
62 | from juju.state.service import ServiceStateManager, parse_service_name |
63 | from juju.control.utils import get_environment |
64 | |
65 | @@ -39,6 +40,10 @@ |
66 | service_name = parse_service_name(unit_name) |
67 | service_state = yield service_manager.get_service_state( |
68 | service_name) |
69 | + if (yield service_state.is_subordinate()): |
70 | + raise JujuError("Subordinate services acquire units from " |
71 | + "their principal service.") |
72 | + |
73 | unit_state = yield service_state.get_unit_state(unit_name) |
74 | yield service_state.remove_unit_state(unit_state) |
75 | log.info("Unit %r removed from service %r", |
76 | |
77 | === modified file 'juju/control/status.py' |
78 | --- juju/control/status.py 2012-03-29 02:50:29 +0000 |
79 | +++ juju/control/status.py 2012-04-05 21:24:20 +0000 |
80 | @@ -7,12 +7,11 @@ |
81 | from twisted.internet.defer import inlineCallbacks, returnValue |
82 | import yaml |
83 | |
84 | -from juju.errors import ProviderError |
85 | from juju.environment.errors import EnvironmentsConfigError |
86 | from juju.state.errors import UnitRelationStateNotFound |
87 | from juju.state.charm import CharmStateManager |
88 | from juju.state.machine import MachineStateManager |
89 | -from juju.state.service import ServiceStateManager |
90 | +from juju.state.service import ServiceStateManager, parse_service_name |
91 | from juju.state.relation import RelationStateManager |
92 | from juju.unit.workflow import WorkflowStateClient |
93 | |
94 | @@ -96,7 +95,9 @@ |
95 | client = yield provider.connect() |
96 | try: |
97 | # Collect status information |
98 | - state = yield collect(scope, provider, client, log) |
99 | + command = StatusCommand(client, provider, log) |
100 | + state = yield command(scope) |
101 | + #state = yield collect(scope, provider, client, log) |
102 | finally: |
103 | yield client.close() |
104 | # Render |
105 | @@ -125,60 +126,222 @@ |
106 | return (services, units) |
107 | |
108 | |
109 | -@inlineCallbacks |
110 | -def collect(scope, machine_provider, client, log): |
111 | - """Extract status information into nested dicts for rendering. |
112 | - |
113 | - `scope`: an optional list of name specifiers. Globbing based |
114 | - wildcards supported. Defaults to all units, services and |
115 | - relations. |
116 | - |
117 | - `machine_provider`: machine provider for the environment |
118 | - |
119 | - `client`: ZK client connection |
120 | - |
121 | - `log`: a Python stdlib logger. |
122 | - """ |
123 | - service_manager = ServiceStateManager(client) |
124 | - relation_manager = RelationStateManager(client) |
125 | - machine_manager = MachineStateManager(client) |
126 | - charm_manager = CharmStateManager(client) |
127 | - |
128 | - service_data = {} |
129 | - machine_data = {} |
130 | - state = dict(services=service_data, machines=machine_data) |
131 | - |
132 | - seen_machines = set() |
133 | - filter_services, filter_units = digest_scope(scope) |
134 | - |
135 | - services = yield service_manager.get_all_service_states() |
136 | - for service in services: |
137 | - if len(filter_services): |
138 | - found = False |
139 | - for filter_service in filter_services: |
140 | - if fnmatch(service.service_name, filter_service): |
141 | - found = True |
142 | - break |
143 | - if not found: |
144 | - continue |
145 | - |
146 | - unit_data = {} |
147 | +class StatusCommand(object): |
148 | + def __init__(self, client, provider, log): |
149 | + """ |
150 | + Callable status command object. |
151 | + |
152 | + `client`: ZK client connection |
153 | + `provider`: machine provider for the environment |
154 | + `log`: a Python stdlib logger. |
155 | + |
156 | + """ |
157 | + self.client = client |
158 | + self.provider = provider |
159 | + self.log = log |
160 | + |
161 | + self.service_manager = ServiceStateManager(client) |
162 | + self.relation_manager = RelationStateManager(client) |
163 | + self.machine_manager = MachineStateManager(client) |
164 | + self.charm_manager = CharmStateManager(client) |
165 | + self._reset() |
166 | + |
167 | + def _reset(self, scope=None): |
168 | + # init per-run state |
169 | + # self.state is assembled by the various process methods |
170 | + # intermediate access to state is made more convenient |
171 | + # using these references to its internals. |
172 | + self.service_data = {} # service name: service info |
173 | + self.machine_data = {} # machine id: machine state |
174 | + self.unit_data = {} # unit_name :unit_info |
175 | + |
176 | + # used in collecting subordinate (which are added to state in a two |
177 | + # phase pass) |
178 | + self.subordinates = {} # service : set(principal service names) |
179 | + |
180 | + self.state = dict(services=self.service_data, |
181 | + machines=self.machine_data) |
182 | + |
183 | + # Filtering info |
184 | + self.seen_machines = set() |
185 | + self.filter_services, self.filter_units = digest_scope(scope) |
186 | + |
187 | + @inlineCallbacks |
188 | + def __call__(self, scope=None): |
189 | + """Extract status information into nested dicts for rendering. |
190 | + |
191 | + `scope`: an optional list of name specifiers. Globbing based wildcards |
192 | + supported. Defaults to all units, services and relations. |
193 | + |
194 | + """ |
195 | + self._reset(scope) |
196 | + |
197 | + # Pass 1 Gather Data (including principals and subordinates) |
198 | + # this builds unit info and container relationships |
199 | + # which is assembled in pass 2 below |
200 | + yield self._process_services() |
201 | + |
202 | + # Pass 2: Nest information according to principal/subordinates |
203 | + # rules |
204 | + self._process_subordinates() |
205 | + |
206 | + yield self._process_machines() |
207 | + |
208 | + returnValue(self.state) |
209 | + |
210 | + @inlineCallbacks |
211 | + def _process_services(self): |
212 | + """ |
213 | + For each service gather the following information:: |
214 | + |
215 | + <service name>: |
216 | + charm: <charm name> |
217 | + exposed: <expose boolean> |
218 | + relations: |
219 | + <relation info -- see _process_relations> |
220 | + units: |
221 | + <unit info -- see _process_units> |
222 | + """ |
223 | + services = yield self.service_manager.get_all_service_states() |
224 | + for service in services: |
225 | + if len(self.filter_services): |
226 | + found = False |
227 | + for filter_service in self.filter_services: |
228 | + if fnmatch(service.service_name, filter_service): |
229 | + found = True |
230 | + break |
231 | + if not found: |
232 | + continue |
233 | + yield self._process_service(service) |
234 | + |
235 | + @inlineCallbacks |
236 | + def _process_service(self, service): |
237 | + """ |
238 | + Gather the service info (described in _process_services). |
239 | + |
240 | + `service`: ServiceState instance |
241 | + """ |
242 | + |
243 | relation_data = {} |
244 | + service_data = self.service_data |
245 | |
246 | charm_id = yield service.get_charm_id() |
247 | - charm = yield charm_manager.get_charm_state(charm_id) |
248 | - |
249 | - service_data[service.service_name] = dict(units=unit_data, |
250 | - charm=charm.id, |
251 | - relations=relation_data) |
252 | - exposed = yield service.get_exposed_flag() |
253 | - if exposed: |
254 | - service_data[service.service_name].update(exposed=exposed) |
255 | - |
256 | + charm = yield self.charm_manager.get_charm_state(charm_id) |
257 | + |
258 | + service_data[service.service_name] = ( |
259 | + dict(units={}, |
260 | + charm=charm.id, |
261 | + relations=relation_data)) |
262 | + |
263 | + if (yield service.is_subordinate()): |
264 | + service_data[service.service_name]["subordinate"] = True |
265 | + |
266 | + yield self._process_expose(service) |
267 | + |
268 | + relations, rel_svc_map = yield self._process_relation_map( |
269 | + service) |
270 | + |
271 | + unit_matched = yield self._process_units(service, |
272 | + relations, |
273 | + rel_svc_map) |
274 | + |
275 | + # after filtering units check if any matched or remove the |
276 | + # service from the output |
277 | + if self.filter_units and not unit_matched: |
278 | + del service_data[service.service_name] |
279 | + return |
280 | + |
281 | + yield self._process_relations(service, relations, rel_svc_map) |
282 | + |
283 | + @inlineCallbacks |
284 | + def _process_units(self, service, relations, rel_svc_map): |
285 | + """ |
286 | + Gather unit information for a service:: |
287 | + |
288 | + <unit name>: |
289 | + agent-state: <started|pendding|etc> |
290 | + machine: <machine id> |
291 | + open-ports: ["port/protocol", ...] |
292 | + public-address: <public dns name or ip> |
293 | + subordinates: |
294 | + <optional nested units of subordinate services> |
295 | + |
296 | + |
297 | + `service`: ServiceState intance |
298 | + `relations`: list of ServiceRelationState instance for this service |
299 | + `rel_svc_map`: maps relation internal ids to the remote endpoint |
300 | + service name. This references the name of the remote |
301 | + endpoint and so is generated per service. |
302 | + """ |
303 | units = yield service.get_all_unit_states() |
304 | unit_matched = False |
305 | |
306 | - relations = yield relation_manager.get_relations_for_service(service) |
307 | + for unit in units: |
308 | + if len(self.filter_units): |
309 | + found = False |
310 | + for filter_unit in self.filter_units: |
311 | + if fnmatch(unit.unit_name, filter_unit): |
312 | + found = True |
313 | + break |
314 | + if not found: |
315 | + continue |
316 | + yield self._process_unit(service, unit, relations, rel_svc_map) |
317 | + unit_matched = True |
318 | + returnValue(unit_matched) |
319 | + |
320 | + @inlineCallbacks |
321 | + def _process_unit(self, service, unit, relations, rel_svc_map): |
322 | + """ Generate unit info for a single unit of a single service. |
323 | + |
324 | + `unit`: ServiceUnitState |
325 | + see `_process_units` for an explanation of other arguments. |
326 | + |
327 | + """ |
328 | + u = self.unit_data[unit.unit_name] = dict() |
329 | + container = yield unit.get_container() |
330 | + |
331 | + if container: |
332 | + u["container"] = container.unit_name |
333 | + self.subordinates.setdefault(unit.service_name, |
334 | + set()).add(container.service_name) |
335 | + |
336 | + machine_id = yield unit.get_assigned_machine_id() |
337 | + u["machine"] = machine_id |
338 | + unit_workflow_client = WorkflowStateClient(self.client, unit) |
339 | + unit_state = yield unit_workflow_client.get_state() |
340 | + if not unit_state: |
341 | + u["agent-state"] = "pending" |
342 | + else: |
343 | + unit_connected = yield unit.has_agent() |
344 | + u["agent-state"] = unit_state.replace("_", "-") \ |
345 | + if unit_connected else "down" |
346 | + |
347 | + exposed = self.service_data[service.service_name].get("exposed") |
348 | + if exposed: |
349 | + open_ports = yield unit.get_open_ports() |
350 | + u["open-ports"] = ["{port}/{proto}".format(**port_info) |
351 | + for port_info in open_ports] |
352 | + |
353 | + u["public-address"] = yield unit.get_public_address() |
354 | + |
355 | + # indicate we should include information about this |
356 | + # machine later |
357 | + self.seen_machines.add(machine_id) |
358 | + |
359 | + # collect info on each relation for the service unit |
360 | + yield self._process_unit_relations(service, unit, |
361 | + relations, rel_svc_map) |
362 | + |
363 | + @inlineCallbacks |
364 | + def _process_relation_map(self, service): |
365 | + """Generate a mapping from a services relations to the service name of |
366 | + the remote endpoints. |
367 | + |
368 | + returns: ([ServiceRelationState, ...], mapping) |
369 | + """ |
370 | + relation_data = self.service_data[service.service_name]["relations"] |
371 | + relation_mgr = self.relation_manager |
372 | + relations = yield relation_mgr.get_relations_for_service(service) |
373 | rel_svc_map = {} |
374 | |
375 | for relation in relations: |
376 | @@ -199,107 +362,182 @@ |
377 | "than 2 endpoints") |
378 | |
379 | rel_service = rel_services[0] |
380 | + relation_data.setdefault(relation.relation_name, set()).add( |
381 | + rel_service.service_name) |
382 | + rel_svc_map[relation.internal_relation_id] = ( |
383 | + rel_service.service_name) |
384 | + |
385 | + returnValue((relations, rel_svc_map)) |
386 | + |
387 | + @inlineCallbacks |
388 | + def _process_relations(self, service, relations, rel_svc_map): |
389 | + """Generate relation information for a given service |
390 | + |
391 | + Each service with relations will have a relations dict |
392 | + nested under it with one or more relations described:: |
393 | + |
394 | + relations: |
395 | + <relation name>: |
396 | + - <remote service name> |
397 | + |
398 | + """ |
399 | + relation_data = self.service_data[service.service_name]["relations"] |
400 | + |
401 | + for relation in relations: |
402 | + rel_services = yield relation.get_service_states() |
403 | + |
404 | + # A single related service implies a peer relation. More |
405 | + # imply a bi-directional provides/requires relationship. |
406 | + # In the later case we omit the local side of the relation |
407 | + # when reporting. |
408 | + if len(rel_services) > 1: |
409 | + # Filter out self from multi-service relations. |
410 | + rel_services = [ |
411 | + rsn for rsn in rel_services if rsn.service_name != |
412 | + service.service_name] |
413 | + |
414 | + if len(rel_services) > 1: |
415 | + raise ValueError("Unexpected relationship with more " |
416 | + "than 2 endpoints") |
417 | + |
418 | + rel_service = rel_services[0] |
419 | relation_data.setdefault( |
420 | - relation.relation_name, set()).add(rel_service.service_name) |
421 | - rel_svc_map[relation.internal_relation_id] = \ |
422 | - rel_service.service_name |
423 | + relation.relation_name, set()).add( |
424 | + rel_service.service_name) |
425 | + rel_svc_map[relation.internal_relation_id] = ( |
426 | + rel_service.service_name) |
427 | |
428 | # Normalize the sets back to lists |
429 | for r in relation_data: |
430 | - relation_data[r] = list(relation_data[r]) |
431 | - |
432 | - for unit in units: |
433 | - if len(filter_units): |
434 | - found = False |
435 | - for filter_unit in filter_units: |
436 | - if fnmatch(unit.unit_name, filter_unit): |
437 | - found = True |
438 | - break |
439 | - if not found: |
440 | - continue |
441 | - |
442 | - u = unit_data[unit.unit_name] = dict() |
443 | - machine_id = yield unit.get_assigned_machine_id() |
444 | - u["machine"] = machine_id |
445 | - unit_workflow_client = WorkflowStateClient(client, unit) |
446 | - unit_state = yield unit_workflow_client.get_state() |
447 | - if not unit_state: |
448 | - u["agent-state"] = "pending" |
449 | + relation_data[r] = sorted(relation_data[r]) |
450 | + |
451 | + @inlineCallbacks |
452 | + def _process_unit_relations(self, service, unit, relations, rel_svc_map): |
453 | + """Collect UnitRelationState information per relation and per unit. |
454 | + |
455 | + Includes information under each unit for its relations including |
456 | + its relation state and information about any possible errors. |
457 | + |
458 | + see `_process_relations` for argument information |
459 | + """ |
460 | + u = self.unit_data[unit.unit_name] |
461 | + relation_errors = {} |
462 | + |
463 | + for relation in relations: |
464 | + try: |
465 | + relation_unit = yield relation.get_unit_state(unit) |
466 | + except UnitRelationStateNotFound: |
467 | + # This exception will occur when relations are |
468 | + # established between services without service |
469 | + # units, and therefore never have any |
470 | + # corresponding service relation units. |
471 | + # UPDATE: common with subordinate services, and |
472 | + # some testing scenarios. |
473 | + continue |
474 | + relation_workflow_client = WorkflowStateClient( |
475 | + self.client, relation_unit) |
476 | + workflow_state = yield relation_workflow_client.get_state() |
477 | + |
478 | + rel_svc_name = rel_svc_map.get(relation.internal_relation_id) |
479 | + if rel_svc_name and workflow_state not in ("up", None): |
480 | + relation_errors.setdefault( |
481 | + relation.relation_name, set()).add(rel_svc_name) |
482 | + |
483 | + if relation_errors: |
484 | + # Normalize sets and store. |
485 | + u["relation-errors"] = dict( |
486 | + [(r, sorted(relation_errors[r])) for r in relation_errors]) |
487 | + |
488 | + def _process_subordinates(self): |
489 | + """Properly nest subordinate units under their principal service's |
490 | + unit nodes. Services and units are generated in one pass, then |
491 | + iterated by this method to structure the output data to reflect |
492 | + actual unit containment. |
493 | + |
494 | + Subordinate units will include the follow:: |
495 | + subordinate: true |
496 | + subordinate-to: |
497 | + - <principal service names> |
498 | + |
499 | + Principal services that have subordinates will include:: |
500 | + |
501 | + subordinates: |
502 | + <subordinate unit name>: |
503 | + agent-state: <agent state> |
504 | + """ |
505 | + service_data = self.service_data |
506 | + |
507 | + for unit_name, u in self.unit_data.iteritems(): |
508 | + container = u.get("container") |
509 | + if container: |
510 | + d = self.unit_data[container].setdefault("subordinates", {}) |
511 | + d[unit_name] = u |
512 | + |
513 | + # remove keys that don't appear in output or come from container |
514 | + for key in ("container", "machine", "public-address"): |
515 | + u.pop(key, None) |
516 | else: |
517 | - unit_connected = yield unit.has_agent() |
518 | - u["agent-state"] = unit_state.replace("_", "-") \ |
519 | - if unit_connected else "down" |
520 | - if exposed: |
521 | - open_ports = yield unit.get_open_ports() |
522 | - u["open-ports"] = ["{port}/{proto}".format(**port_info) |
523 | - for port_info in open_ports] |
524 | - |
525 | - u["public-address"] = yield unit.get_public_address() |
526 | - |
527 | - # indicate we should include information about this |
528 | - # machine later |
529 | - seen_machines.add(machine_id) |
530 | - unit_matched = True |
531 | - |
532 | - # collect info on each relation for the service unit |
533 | - relation_errors = {} |
534 | - for relation in relations: |
535 | - try: |
536 | - relation_unit = yield relation.get_unit_state(unit) |
537 | - except UnitRelationStateNotFound: |
538 | - # This exception will occur when relations are |
539 | - # established between services without service |
540 | - # units, and therefore never have any |
541 | - # corresponding service relation units. |
542 | - # UPDATE: common with subordinate services, and |
543 | - # some testing scenarios. |
544 | - continue |
545 | - relation_workflow_client = WorkflowStateClient( |
546 | - client, relation_unit) |
547 | - workflow_state = yield relation_workflow_client.get_state() |
548 | - |
549 | - rel_svc_name = rel_svc_map.get(relation.internal_relation_id) |
550 | - if rel_svc_name and workflow_state not in ("up", None): |
551 | - relation_errors.setdefault( |
552 | - relation.relation_name, set()).add(rel_svc_name) |
553 | - |
554 | - if relation_errors: |
555 | - # Normalize sets and store. |
556 | - u["relation-errors"] = dict( |
557 | - [(r, list(relation_errors[r])) for r in relation_errors]) |
558 | - |
559 | - # after filtering units check if any matched or remove the |
560 | - # service from the output |
561 | - if filter_units and not unit_matched: |
562 | - del service_data[service.service_name] |
563 | - continue |
564 | - |
565 | - machines = yield machine_manager.get_all_machine_states() |
566 | - for machine_state in machines: |
567 | - if (filter_services or filter_units) and \ |
568 | - machine_state.id not in seen_machines: |
569 | - continue |
570 | - |
571 | + service_name = parse_service_name(unit_name) |
572 | + service_data[service_name]["units"][unit_name] = u |
573 | + |
574 | + for sub_service, principal_services in self.subordinates.iteritems(): |
575 | + service_data[sub_service]["subordinate-to"] = sorted(principal_services) |
576 | + service_data[sub_service].pop("units", None) |
577 | + |
578 | + @inlineCallbacks |
579 | + def _process_expose(self, service): |
580 | + """Indicate if a service is exposed or not.""" |
581 | + exposed = yield service.get_exposed_flag() |
582 | + if exposed: |
583 | + self.service_data[service.service_name].update(exposed=exposed) |
584 | + returnValue(exposed) |
585 | + |
586 | + @inlineCallbacks |
587 | + def _process_machines(self): |
588 | + """Gather machine information. |
589 | + |
590 | + machines: |
591 | + <machine id>: |
592 | + agent-state: <agent state> |
593 | + dns-name: <dns name> |
594 | + instance-id: <provider specific instance id> |
595 | + instance-state: <instance state> |
596 | + """ |
597 | + |
598 | + machines = yield self.machine_manager.get_all_machine_states() |
599 | + for machine_state in machines: |
600 | + if (self.filter_services or self.filter_units) and \ |
601 | + machine_state.id not in self.seen_machines: |
602 | + continue |
603 | + yield self._process_machine(machine_state) |
604 | + |
605 | + @inlineCallbacks |
606 | + def _process_machine(self, machine_state): |
607 | + """ |
608 | + `machine_state`: MachineState instance |
609 | + """ |
610 | instance_id = yield machine_state.get_instance_id() |
611 | m = {"instance-id": instance_id \ |
612 | if instance_id is not None else "pending"} |
613 | if instance_id is not None: |
614 | try: |
615 | - pm = yield machine_provider.get_machine(instance_id) |
616 | + pm = yield self.provider.get_machine(instance_id) |
617 | m["dns-name"] = pm.dns_name |
618 | m["instance-state"] = pm.state |
619 | if (yield machine_state.has_agent()): |
620 | # if the agent's connected, we're fine |
621 | m["agent-state"] = "running" |
622 | else: |
623 | - units = (yield machine_state.get_all_service_unit_states()) |
624 | + units = ( |
625 | + yield machine_state.get_all_service_unit_states()) |
626 | for unit in units: |
627 | unit_workflow_client = WorkflowStateClient( |
628 | - client, unit) |
629 | + self.client, unit) |
630 | if (yield unit_workflow_client.get_state()): |
631 | - # for unit to have a state, its agent must have |
632 | - # run, which implies the machine agent must have |
633 | - # been running correctly at some point in the past |
634 | + # for unit to have a state, its agent must |
635 | + # have run, which implies the machine agent |
636 | + # must have been running correctly at some |
637 | + # point in the past |
638 | m["agent-state"] = "down" |
639 | break |
640 | else: |
641 | @@ -307,13 +545,11 @@ |
642 | m["agent-state"] = "not-started" |
643 | except ProviderError: |
644 | # The provider doesn't have machine information |
645 | - log.error( |
646 | + self.log.error( |
647 | "Machine provider information missing: machine %s" % ( |
648 | machine_state.id)) |
649 | |
650 | - machine_data[machine_state.id] = m |
651 | - |
652 | - returnValue(state) |
653 | + self.machine_data[machine_state.id] = m |
654 | |
655 | |
656 | def render_yaml(data, filelike, environment): |
657 | @@ -356,6 +592,15 @@ |
658 | "shape": "box", |
659 | "style": "filled", |
660 | }, |
661 | + |
662 | + "subunit": { |
663 | + "color": "#c9c9c9", |
664 | + "fontcolor": "#ffffff", |
665 | + "shape": "box", |
666 | + "style": "filled", |
667 | + "rank": "same" |
668 | + }, |
669 | + |
670 | "relation": { |
671 | "dir": "none"} |
672 | } |
673 | @@ -364,9 +609,10 @@ |
674 | def safe_dot_label(name): |
675 | """Convert a name to a label safe for use in DOT. |
676 | |
677 | - Works around an issue where service names like wiki-db will |
678 | - produce DOT items with names like cluster_wiki-db where the |
679 | - trailing '-' invalidates the name. |
680 | + Works around an issue where service names like wiki-db will produce DOT |
681 | + items with names like cluster_wiki-db where the trailing '-' invalidates |
682 | + the name. |
683 | + |
684 | """ |
685 | return name.replace("-", "_") |
686 | |
687 | @@ -399,34 +645,54 @@ |
688 | **style["service"]) |
689 | cluster.add_node(snode) |
690 | |
691 | - for unit_name, unit in service["units"].iteritems(): |
692 | - un = pydot.Node(safe_dot_label(unit_name), |
693 | - label="<%s<br/><i>%s</i>>" % ( |
694 | - unit_name, |
695 | - unit.get("public-address")), |
696 | - **style["unit"]) |
697 | - cluster.add_node(un) |
698 | + for unit_name, unit in service.get("units", {}).iteritems(): |
699 | + subordinates = unit.get("subordinates") |
700 | + if subordinates: |
701 | + container = pydot.Subgraph() |
702 | + un = pydot.Node(safe_dot_label(unit_name), |
703 | + label="<%s<br/><i>%s</i>>" % ( |
704 | + unit_name, |
705 | + unit.get("public-address")), |
706 | + **style["unit"]) |
707 | + container.add_node(un) |
708 | + for sub in subordinates: |
709 | + s = pydot.Node(safe_dot_label(sub), |
710 | + label="<%s<br/>>" % (sub), |
711 | + **style["subunit"]) |
712 | + container.add_node(s) |
713 | + container.add_edge(pydot.Edge(un, s, **style["relation"])) |
714 | + cluster.add_subgraph(container) |
715 | + else: |
716 | + un = pydot.Node(safe_dot_label(unit_name), |
717 | + label="<%s<br/><i>%s</i>>" % ( |
718 | + unit_name, |
719 | + unit.get("public-address")), |
720 | + **style["unit"]) |
721 | + cluster.add_node(un) |
722 | |
723 | cluster.add_edge(pydot.Edge(snode, un)) |
724 | |
725 | dot.add_subgraph(cluster) |
726 | |
727 | # now map the relationships |
728 | - for kind, relations in service["relations"].iteritems(): |
729 | - for relation in relations: |
730 | - src = safe_dot_label(relation) |
731 | + for kind, relation in service["relations"].iteritems(): |
732 | + if not isinstance(relation, list): |
733 | + relation = (relation,) |
734 | + for rel in relation: |
735 | + src = safe_dot_label(rel) |
736 | dest = safe_dot_label(service_name) |
737 | - descriptor = tuple(sorted((src, dest))) |
738 | + descriptor = ":".join(tuple(sorted((src, dest)))) |
739 | + kind = safe_dot_label("%s/%s" % (descriptor, kind)) |
740 | if descriptor not in seen_relations: |
741 | seen_relations.add(descriptor) |
742 | dot.add_edge(pydot.Edge( |
743 | - src, |
744 | - dest, |
745 | - label=kind, |
746 | - **style["relation"] |
747 | - )) |
748 | + src, |
749 | + dest, |
750 | + label=kind, |
751 | + **style["relation"] |
752 | + )) |
753 | |
754 | - if format == 'dot': |
755 | + if format == "dot": |
756 | filelike.write(dot.to_string()) |
757 | else: |
758 | filelike.write(dot.create(format=format)) |
759 | |
760 | === modified file 'juju/control/tests/test_add_unit.py' |
761 | --- juju/control/tests/test_add_unit.py 2012-03-29 13:40:45 +0000 |
762 | +++ juju/control/tests/test_add_unit.py 2012-04-05 21:24:20 +0000 |
763 | @@ -97,6 +97,19 @@ |
764 | "Service 'volcano' was not found", self.stderr.getvalue()) |
765 | |
766 | @inlineCallbacks |
767 | + def test_add_unit_subordinate_service(self): |
768 | + yield self.add_service_from_charm("logging") |
769 | + finished = self.setup_cli_reactor() |
770 | + self.setup_exit(0) |
771 | + self.mocker.replay() |
772 | + main(["add-unit", "logging"]) |
773 | + yield finished |
774 | + self.assertIn( |
775 | + "Subordinate services acquire units from " |
776 | + "their principal service.", |
777 | + self.stderr.getvalue()) |
778 | + |
779 | + @inlineCallbacks |
780 | def test_add_unit_reuses_machines(self): |
781 | """Verify that if machines are not in use, add-unit uses them.""" |
782 | # add machine to wordpress, then destroy and reallocate later |
783 | @@ -107,6 +120,7 @@ |
784 | wordpress_machine_state = yield self.add_machine_state() |
785 | yield wordpress_unit_state.assign_to_machine(wordpress_machine_state) |
786 | yield wordpress_unit_state.unassign_from_machine() |
787 | + |
788 | finished = self.setup_cli_reactor() |
789 | self.setup_exit(0) |
790 | self.mocker.replay() |
791 | |
792 | === modified file 'juju/control/tests/test_deploy.py' |
793 | --- juju/control/tests/test_deploy.py 2012-03-29 03:20:51 +0000 |
794 | +++ juju/control/tests/test_deploy.py 2012-04-05 21:24:20 +0000 |
795 | @@ -506,6 +506,33 @@ |
796 | self.assertEqual(machine_id, 0) |
797 | |
798 | @inlineCallbacks |
799 | + |
800 | + def test_deploy_informs_with_subordinate(self): |
801 | + """Verify subordinate charm doesn't deploy. |
802 | + |
803 | + And that it properly notifies the user. |
804 | + """ |
805 | + log = self.capture_logging() |
806 | + finished = self.setup_cli_reactor() |
807 | + self.setup_exit(0) |
808 | + self.mocker.replay() |
809 | + |
810 | + # missing config file |
811 | + main(["deploy", |
812 | + "--repository", self.unbundled_repo_path, "local:logging"]) |
813 | + yield finished |
814 | + self.assertIn( |
815 | + "Subordinate 'logging' awaiting relationship to " |
816 | + "principal for deployment.\n", |
817 | + log.getvalue()) |
818 | + |
819 | + # and verify no units assigned to service |
820 | + service_state = yield self.service_state_manager.get_service_state("logging") |
821 | + self.assertEqual(service_state.service_name, "logging") |
822 | + |
823 | + units = yield service_state.get_unit_names() |
824 | + self.assertEqual(units, []) |
825 | + |
826 | def test_deploy_legacy_keys_in_legacy_env(self): |
827 | yield self.client.delete("/constraints") |
828 | |
829 | |
830 | === modified file 'juju/control/tests/test_remove_unit.py' |
831 | --- juju/control/tests/test_remove_unit.py 2012-03-27 22:46:44 +0000 |
832 | +++ juju/control/tests/test_remove_unit.py 2012-04-05 21:24:20 +0000 |
833 | @@ -139,6 +139,18 @@ |
834 | "Service 'volcano' was not found", self.stderr.getvalue()) |
835 | |
836 | @inlineCallbacks |
837 | + def test_remove_unit_with_subordinate(self): |
838 | + yield self.add_service_from_charm("logging") |
839 | + finished = self.setup_cli_reactor() |
840 | + self.setup_exit(0) |
841 | + self.mocker.replay() |
842 | + main(["remove-unit", "logging/0"]) |
843 | + yield finished |
844 | + self.assertIn( |
845 | + "Subordinate services acquire units from their principal service.", |
846 | + self.stderr.getvalue()) |
847 | + |
848 | + @inlineCallbacks |
849 | def test_remove_unit_bad_parse(self): |
850 | """Verify that a bad service unit name results in an appropriate error. |
851 | """ |
852 | |
853 | === modified file 'juju/control/tests/test_status.py' |
854 | --- juju/control/tests/test_status.py 2012-03-30 10:13:09 +0000 |
855 | +++ juju/control/tests/test_status.py 2012-04-05 21:24:20 +0000 |
856 | @@ -33,6 +33,17 @@ |
857 | fp.close() |
858 | |
859 | |
860 | +@inlineCallbacks |
861 | +def collect(scope, provider, client, log): |
862 | + """Collect and return status info as dict""" |
863 | + # provided for backwards compatibility with |
864 | + # original API |
865 | + # used only in testing |
866 | + s = status.StatusCommand(client, provider, log) |
867 | + state = yield s(scope) |
868 | + returnValue(state) |
869 | + |
870 | + |
871 | class StatusTestBase(ServiceStateManagerTestBase, ControlToolTest): |
872 | |
873 | # Status tests setup a large tree every time, make allowances for it. |
874 | @@ -64,8 +75,7 @@ |
875 | @inlineCallbacks |
876 | def add_relation_unit_states(self, relation_state, unit_states, states): |
877 | for unit_state, state in zip(unit_states, states): |
878 | - relation_unit_state = yield relation_state.add_unit_state( |
879 | - unit_state) |
880 | + relation_unit_state = yield relation_state.add_unit_state(unit_state) |
881 | workflow_client = ZookeeperWorkflowState( |
882 | self.client, relation_unit_state) |
883 | with (yield workflow_client.lock()): |
884 | @@ -76,6 +86,7 @@ |
885 | self, |
886 | source_endpoint, source_units, source_states, |
887 | dest_endpoint, dest_units, dest_states): |
888 | + |
889 | relation_state, service_relation_states = \ |
890 | yield self.relation_state_manager.add_relation_state( |
891 | *[source_endpoint, dest_endpoint]) |
892 | @@ -86,11 +97,17 @@ |
893 | dest_relation_state, dest_units, dest_states) |
894 | |
895 | @inlineCallbacks |
896 | - def add_unit(self, service, machine, with_agent=lambda _: True): |
897 | - unit = yield service.add_unit_state() |
898 | - yield unit.assign_to_machine(machine) |
899 | + def add_unit(self, service, machine, with_agent=lambda _: True, |
900 | + units=None, container=None): |
901 | + unit = yield service.add_unit_state(container=container) |
902 | + self.assertTrue(machine or container) |
903 | + if machine is not None: |
904 | + yield unit.assign_to_machine(machine) |
905 | if with_agent(unit.unit_name): |
906 | yield unit.connect_agent() |
907 | + if units is not None: |
908 | + units.setdefault(service.service_name, []).append(unit) |
909 | + |
910 | returnValue(unit) |
911 | |
912 | @inlineCallbacks |
913 | @@ -155,15 +172,19 @@ |
914 | if fnmatch(name, pattern): |
915 | return False |
916 | return True |
917 | - wpu = yield self.add_unit(wordpress, m1, with_unit) |
918 | - myu1 = yield self.add_unit(mysql, m2, with_unit) |
919 | - myu2 = yield self.add_unit(mysql, m3, with_unit) |
920 | - vu1 = yield self.add_unit(varnish, m4, with_unit) |
921 | - vu2 = yield self.add_unit(varnish, m5, with_unit) |
922 | - mc1 = yield self.add_unit(memcache, m6, with_unit) |
923 | - mc2 = yield self.add_unit(memcache, m7, with_unit) |
924 | - |
925 | - # add unit states to services and assign to machines |
926 | + |
927 | + |
928 | + units = {} |
929 | + wpu = yield self.add_unit(wordpress, m1, with_unit, units) |
930 | + myu1 = yield self.add_unit(mysql, m2, with_unit, units) |
931 | + myu2 = yield self.add_unit(mysql, m3, with_unit, units) |
932 | + vu1 = yield self.add_unit(varnish, m4, with_unit, units) |
933 | + vu2 = yield self.add_unit(varnish, m5, with_unit, units) |
934 | + mc1 = yield self.add_unit(memcache, m6, with_unit, units) |
935 | + mc2 = yield self.add_unit(memcache, m7, with_unit, units) |
936 | + state["units"] = units |
937 | + |
938 | + # add unit states to services and assign to machines |
939 | # Set the lifecycle state and open ports, if any, for each unit state. |
940 | yield self.set_unit_state(wpu, "started", [(80, "tcp"), (443, "tcp")]) |
941 | yield self.set_unit_state(myu1, "started") |
942 | @@ -250,7 +271,7 @@ |
943 | yield riak_u1_workflow.set_state("up") |
944 | yield peer_rel.add_unit_state(riak_u2) |
945 | |
946 | - state = yield status.collect( |
947 | + state = yield collect( |
948 | ["riak"], self.provider, self.client, None) |
949 | self.assertEqual( |
950 | state["services"]["riak"], |
951 | @@ -295,15 +316,15 @@ |
952 | mysql_ep, [mysql_1], ["up"], |
953 | teamblog_db_ep, [teamblog_1], ["up"]) |
954 | |
955 | - state = yield status.collect(None, self.provider, self.client, None) |
956 | - self.assertEqual( |
957 | - state['services']['mysql']['units']['mysql/0'], |
958 | - {'agent-state': 'pending', |
959 | - 'machine': 0, |
960 | - 'public-address': None}) |
961 | - self.assertEqual( |
962 | - state['services']['mysql']['relations'], |
963 | - {'db': ['myblog', 'teamblog']}) |
964 | + state = yield collect(None, self.provider, self.client, None) |
965 | + self.assertEqual( |
966 | + state["services"]["mysql"]["units"]["mysql/0"], |
967 | + {"agent-state": "pending", |
968 | + "machine": 0, |
969 | + "public-address": None}) |
970 | + self.assertEqual( |
971 | + state["services"]["mysql"]["relations"], |
972 | + {"db": ["myblog", "teamblog"]}) |
973 | |
974 | @inlineCallbacks |
975 | def test_service_with_multiple_rels_to_same_endpoint(self): |
976 | @@ -330,18 +351,18 @@ |
977 | mysql_ep, [mysql_1], ["down"], |
978 | read_db_ep, [myblog_1], ["up"]) |
979 | |
980 | - state = yield status.collect(None, self.provider, self.client, None) |
981 | + state = yield collect(None, self.provider, self.client, None) |
982 | # Even though there are two relations to this service we |
983 | # collapse to one the additional displays are redundant. |
984 | self.assertEqual( |
985 | - state['services']['mysql']['relations'], |
986 | - {'db': ['myblog']}) |
987 | + state["services"]["mysql"]["relations"], |
988 | + {"db": ["myblog"]}) |
989 | self.assertEqual( |
990 | - state['services']['mysql']['units']['mysql/0'], |
991 | - {'agent-state': 'pending', |
992 | - 'machine': 0, |
993 | - 'relation-errors': {'db': ['myblog']}, |
994 | - 'public-address': None}) |
995 | + state["services"]["mysql"]["units"]["mysql/0"], |
996 | + {"agent-state": "pending", |
997 | + "machine": 0, |
998 | + "relation-errors": {"db": ["myblog"]}, |
999 | + "public-address": None}) |
1000 | |
1001 | @inlineCallbacks |
1002 | def test_collect(self): |
1003 | @@ -359,7 +380,7 @@ |
1004 | yield agent.start() |
1005 | |
1006 | # collect everything |
1007 | - state = yield status.collect(None, self.provider, self.client, None) |
1008 | + state = yield collect(None, self.provider, self.client, None) |
1009 | services = state["services"] |
1010 | self.assertIn("wordpress", services) |
1011 | self.assertIn("varnish", services) |
1012 | @@ -404,7 +425,7 @@ |
1013 | self.assertEqual( |
1014 | services["wordpress"], |
1015 | {"charm": "local:series/wordpress-3", |
1016 | - "relations": { |
1017 | + "relations": { |
1018 | "cache": ["memcache"], |
1019 | "db": ["mysql"], |
1020 | "proxy": ["varnish"]}, |
1021 | @@ -436,7 +457,7 @@ |
1022 | yield self.build_topology() |
1023 | |
1024 | # collect by service name |
1025 | - state = yield status.collect( |
1026 | + state = yield collect( |
1027 | ["wordpress"], self.provider, self.client, None) |
1028 | # Validate that only the expected service is present |
1029 | # in the state |
1030 | @@ -444,13 +465,13 @@ |
1031 | self.assertEqual(state["services"].keys(), ["wordpress"]) |
1032 | |
1033 | # collect by unit name |
1034 | - state = yield status.collect(["*/0"], self.provider, self.client, None) |
1035 | + state = yield collect(["*/0"], self.provider, self.client, None) |
1036 | self.assertEqual(set(state["machines"].keys()), set([0, 1, 3, 5])) |
1037 | self.assertEqual(set(state["services"].keys()), |
1038 | set(["memcache", "varnish", "mysql", "wordpress"])) |
1039 | |
1040 | # collect by unit name |
1041 | - state = yield status.collect(["*/1"], self.provider, self.client, None) |
1042 | + state = yield collect(["*/1"], self.provider, self.client, None) |
1043 | self.assertEqual(set(state["machines"].keys()), set([2, 4, 6])) |
1044 | |
1045 | # verify that only the proper units and services are present |
1046 | @@ -489,13 +510,13 @@ |
1047 | }}}}) |
1048 | |
1049 | # filter a missing service |
1050 | - state = yield status.collect( |
1051 | + state = yield collect( |
1052 | ["cluehammer"], self.provider, self.client, None) |
1053 | self.assertEqual(set(state["machines"].keys()), set([])) |
1054 | self.assertEqual(set(state["services"].keys()), set([])) |
1055 | |
1056 | # filter a missing unit |
1057 | - state = yield status.collect(["*/7"], self.provider, self.client, None) |
1058 | + state = yield collect(["*/7"], self.provider, self.client, None) |
1059 | self.assertEqual(set(state["machines"].keys()), set([])) |
1060 | self.assertEqual(set(state["services"].keys()), set([])) |
1061 | |
1062 | @@ -512,7 +533,7 @@ |
1063 | yield unit.unassign_from_machine() |
1064 | yield unit.set_public_address(None) |
1065 | # test that the machine is in state information w/o assignment |
1066 | - state = yield status.collect(None, self.provider, self.client, None) |
1067 | + state = yield collect(None, self.provider, self.client, None) |
1068 | # verify that the unassigned machine appears in the state |
1069 | self.assertEqual(state["machines"][machine_id], |
1070 | {"dns-name": "steamcloud-1.com", |
1071 | @@ -544,7 +565,7 @@ |
1072 | yield wordpress.remove_unit_state(unit) |
1073 | |
1074 | # test that wordpress has no assigned service units |
1075 | - state = yield status.collect(None, self.provider, self.client, None) |
1076 | + state = yield collect(None, self.provider, self.client, None) |
1077 | self.assertEqual( |
1078 | state["services"]["wordpress"], |
1079 | {"charm": "local:series/wordpress-3", |
1080 | @@ -576,7 +597,7 @@ |
1081 | yield wpu.assign_to_machine(m8) |
1082 | |
1083 | # test that we identify we don't have machine state |
1084 | - state = yield status.collect( |
1085 | + state = yield collect( |
1086 | None, self.provider, self.client, logging.getLogger()) |
1087 | self.assertEqual(state["machines"][7]["instance-id"], |
1088 | "pending") |
1089 | @@ -715,13 +736,16 @@ |
1090 | self.assertIn("mysql -> wordpress", result) |
1091 | |
1092 | # assert that properties were applied to a relationship |
1093 | - self.assertIn("wordpress -> varnish [dir=none, label=proxy]", |
1094 | + self.assertIn("wordpress -> varnish [dir=none, " |
1095 | + "label=\"varnish:wordpress/proxy\"]", |
1096 | result) |
1097 | |
1098 | # verify that the renderer picked up the DNS name of the |
1099 | # machines (and they are associated with the proper machine) |
1100 | self.assertIn( |
1101 | - '"mysql/0" [color="#DD4814", fontcolor="#ffffff", shape=box, style=filled, label=<mysql/0<br/><i>mysql-0.example.com</i>>]', |
1102 | + '"mysql/0" [color="#DD4814", fontcolor="#ffffff", ' |
1103 | + "shape=box, style=filled, label=<mysql/0<br/><i>mysql-0." |
1104 | + "example.com</i>>]", |
1105 | result) |
1106 | self.assertIn( |
1107 | '"mysql/1" [color="#DD4814", fontcolor="#ffffff", shape=box, style=filled, label=<mysql/1<br/><i>mysql-1.example.com</i>>]', |
1108 | @@ -771,3 +795,100 @@ |
1109 | |
1110 | # look for a hint the process completed. |
1111 | self.assertIn("</svg>", self.output.getvalue()) |
1112 | + |
1113 | + @inlineCallbacks |
1114 | + def test_subordinate_status_output(self): |
1115 | + state = yield self.build_topology() |
1116 | + # supplement status with additional subordinates |
1117 | + # add logging to mysql and wordpress |
1118 | + logging = yield self.add_service_from_charm("logging") |
1119 | + |
1120 | + mysql_ep = RelationEndpoint("mysql", "client-server", |
1121 | + "juju-info", "server") |
1122 | + wordpress_db_ep = RelationEndpoint("wordpress", "client-server", |
1123 | + "juju-info", "server") |
1124 | + logging_ep = RelationEndpoint("logging", "client-server", |
1125 | + "juju-info", "client", "container") |
1126 | + |
1127 | + my_log_rel, my_log_services = ( |
1128 | + yield self.relation_state_manager.add_relation_state( |
1129 | + mysql_ep, logging_ep)) |
1130 | + wp_log_rel, wp_log_services = ( |
1131 | + yield self.relation_state_manager.add_relation_state( |
1132 | + wordpress_db_ep, logging_ep)) |
1133 | + |
1134 | + units = state["units"] |
1135 | + log_units = units.setdefault("logging", {}) |
1136 | + wp1 = iter(units["wordpress"]).next() |
1137 | + mu1, mu2 = list(units["mysql"]) |
1138 | + |
1139 | + yield self.add_unit(logging, None, container=mu1, units=log_units) |
1140 | + yield self.add_unit(logging, None, container=wp1, units=log_units) |
1141 | + yield self.add_unit(logging, None, container=mu2, units=log_units) |
1142 | + |
1143 | + self.mock_environment() |
1144 | + self.mocker.replay() |
1145 | + |
1146 | + yield status.status(self.environment, [], |
1147 | + status.render_yaml, self.output, None) |
1148 | + |
1149 | + state = yaml.load(self.output.getvalue()) |
1150 | + |
1151 | + # verify our changes |
1152 | + log_state = state["services"]["logging"] |
1153 | + |
1154 | + self.assertEqual(set(log_state["relations"]["juju-info"]), |
1155 | + set(["mysql", "wordpress"])) |
1156 | + self.assertEqual(set(log_state["subordinate-to"]), |
1157 | + set(["mysql", "wordpress"])) |
1158 | + |
1159 | + wp_state = state["services"]["wordpress"] |
1160 | + self.assertEqual(wp_state["relations"]["juju-info"], ["logging"]) |
1161 | + wp_subs = wp_state["units"]["wordpress/0"]["subordinates"] |
1162 | + logging_sub = wp_subs["logging/1"] |
1163 | + # this assertion verifies that we don't see keys we don't |
1164 | + # expect as well |
1165 | + self.assertEqual(logging_sub, {"agent-state": "pending"}) |
1166 | + |
1167 | + @inlineCallbacks |
1168 | + def test_subordinate_status_output_no_container(self): |
1169 | + state = yield self.build_topology() |
1170 | + # supplement status with additional subordinates |
1171 | + # add logging to mysql and wordpress |
1172 | + logging = yield self.add_service_from_charm("logging") |
1173 | + |
1174 | + mysql_ep = RelationEndpoint("mysql", "client-server", |
1175 | + "juju-info", "server") |
1176 | + wordpress_db_ep = RelationEndpoint("wordpress", "client-server", |
1177 | + "juju-info", "server") |
1178 | + logging_ep = RelationEndpoint("logging", "client-server", |
1179 | + "juju-info", "client", "container") |
1180 | + |
1181 | + my_log_rel, my_log_services = ( |
1182 | + yield self.relation_state_manager.add_relation_state( |
1183 | + mysql_ep, logging_ep)) |
1184 | + wp_log_rel, wp_log_services = ( |
1185 | + yield self.relation_state_manager.add_relation_state( |
1186 | + wordpress_db_ep, logging_ep)) |
1187 | + |
1188 | + units = state["units"] |
1189 | + log_units = units.setdefault("logging", {}) |
1190 | + wp1 = iter(units["wordpress"]).next() |
1191 | + mu1, mu2 = list(units["mysql"]) |
1192 | + |
1193 | + yield self.add_unit(logging, None, container=mu1, units=log_units) |
1194 | + yield self.add_unit(logging, None, container=wp1, units=log_units) |
1195 | + yield self.add_unit(logging, None, container=mu2, units=log_units) |
1196 | + |
1197 | + # remove mysql/0 |
1198 | + yield state["services"]["mysql"].remove_unit_state(mu1) |
1199 | + |
1200 | + self.mock_environment() |
1201 | + self.mocker.replay() |
1202 | + |
1203 | + yield status.status(self.environment, [], |
1204 | + status.render_yaml, self.output, None) |
1205 | + |
1206 | + output = yaml.load(self.output.getvalue()) |
1207 | + self.assertNotIn(mu1.unit_name, output["services"]["mysql"]["units"]) |
1208 | + self.assertIn(mu2.unit_name, output["services"]["mysql"]["units"]) |
1209 | |
1210 | === modified file 'juju/state/service.py' |
1211 | --- juju/state/service.py 2012-03-30 10:13:09 +0000 |
1212 | +++ juju/state/service.py 2012-04-05 21:24:20 +0000 |
1213 | @@ -23,6 +23,7 @@ |
1214 | from juju.state.charm import CharmStateManager |
1215 | from juju.state.relation import ServiceRelationState, RelationStateManager |
1216 | from juju.state.machine import _public_machine_id, MachineState |
1217 | +from juju.state.topology import InternalTopologyError |
1218 | from juju.state.utils import ( |
1219 | remove_tree, dict_merge, YAMLState, YAMLStateNodeMixin) |
1220 | |
1221 | @@ -866,7 +867,12 @@ |
1222 | |
1223 | """ |
1224 | topology = yield self._read_topology() |
1225 | - container_info = topology.get_service_unit_container(self.internal_id) |
1226 | + try: |
1227 | + container_info = topology.get_service_unit_container( |
1228 | + self.internal_id) |
1229 | + except InternalTopologyError: |
1230 | + returnValue(None) |
1231 | + |
1232 | if container_info is None: |
1233 | returnValue(None) |
1234 | |
1235 | |
1236 | === modified file 'juju/state/tests/test_charm.py' |
1237 | --- juju/state/tests/test_charm.py 2012-03-16 10:11:36 +0000 |
1238 | +++ juju/state/tests/test_charm.py 2012-04-05 21:24:20 +0000 |
1239 | @@ -1,17 +1,19 @@ |
1240 | +import os |
1241 | +import shutil |
1242 | + |
1243 | import yaml |
1244 | |
1245 | from twisted.internet.defer import inlineCallbacks |
1246 | |
1247 | from juju.charm.directory import CharmDirectory |
1248 | from juju.charm.tests import local_charm_id |
1249 | +from juju.charm.tests.test_directory import sample_directory |
1250 | from juju.charm.tests.test_repository import unbundled_repository |
1251 | + |
1252 | from juju.state.charm import CharmStateManager |
1253 | from juju.state.errors import CharmStateNotFound |
1254 | from juju.state.tests.common import StateTestBase |
1255 | |
1256 | -import os |
1257 | -import shutil |
1258 | - |
1259 | |
1260 | class CharmStateManagerTest(StateTestBase): |
1261 | |
1262 | @@ -107,6 +109,20 @@ |
1263 | "local:series/dummy-1") |
1264 | metadata = yield charm_state.get_metadata() |
1265 | self.assertEquals(metadata.name, "dummy") |
1266 | + self.assertFalse(metadata.is_subordinate) |
1267 | + self.assertFalse(charm_state.is_subordinate()) |
1268 | + |
1269 | + @inlineCallbacks |
1270 | + def test_charm_state_is_subordinate(self): |
1271 | + log_dir = os.path.join(os.path.dirname(sample_directory), "logging") |
1272 | + charm = CharmDirectory(log_dir) |
1273 | + yield self.charm_state_manager.add_charm_state( |
1274 | + "local:series/logging-1", charm, "") |
1275 | + charm_state = yield self.charm_state_manager.get_charm_state( |
1276 | + "local:series/logging-1") |
1277 | + |
1278 | + self.assertTrue(charm_state.is_subordinate) |
1279 | + |
1280 | |
1281 | @inlineCallbacks |
1282 | def test_charm_state_config_options(self): |
1283 | |
1284 | === modified file 'juju/state/tests/test_service.py' |
1285 | --- juju/state/tests/test_service.py 2012-03-30 10:13:09 +0000 |
1286 | +++ juju/state/tests/test_service.py 2012-04-05 21:24:20 +0000 |
1287 | @@ -11,2880 +11,2921 @@ |
1288 | from juju.charm.tests import local_charm_id |
1289 | from juju.charm.tests.test_metadata import test_repository_path |
1290 | from juju.machine.tests.test_constraints import ( |
1291 | - dummy_constraints, dummy_cs, series_constraints) |
1292 | + dummy_constraints, dummy_cs, series_constraints) |
1293 | from juju.lib.pick import pick_attr |
1294 | from juju.state.charm import CharmStateManager |
1295 | from juju.charm.tests.test_repository import unbundled_repository |
1296 | from juju.state.endpoint import RelationEndpoint |
1297 | from juju.state.service import ( |
1298 | - ServiceStateManager, NO_HOOKS, RETRY_HOOKS, parse_service_name) |
1299 | + ServiceStateManager, NO_HOOKS, RETRY_HOOKS, parse_service_name) |
1300 | from juju.state.machine import MachineStateManager |
1301 | from juju.state.relation import RelationStateManager |
1302 | from juju.state.utils import YAMLState |
1303 | from juju.state.errors import ( |
1304 | - StateChanged, ServiceStateNotFound, ServiceUnitStateNotFound, |
1305 | - ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse, |
1306 | - BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled, |
1307 | - MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled, |
1308 | - ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher, |
1309 | - ServiceUnitUpgradeAlreadyEnabled) |
1310 | + StateChanged, ServiceStateNotFound, ServiceUnitStateNotFound, |
1311 | + ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse, |
1312 | + BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled, |
1313 | + MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled, |
1314 | + ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher, |
1315 | + ServiceUnitUpgradeAlreadyEnabled) |
1316 | |
1317 | from juju.state.tests.common import StateTestBase |
1318 | |
1319 | |
1320 | class ServiceStateManagerTestBase(StateTestBase): |
1321 | |
1322 | - @inlineCallbacks |
1323 | - def setUp(self): |
1324 | - yield super(ServiceStateManagerTestBase, self).setUp() |
1325 | - yield self.push_default_config() |
1326 | - self.charm_state_manager = CharmStateManager(self.client) |
1327 | - self.service_state_manager = ServiceStateManager(self.client) |
1328 | - self.machine_state_manager = MachineStateManager(self.client) |
1329 | - self.charm_state = yield self.charm_state_manager.add_charm_state( |
1330 | - local_charm_id(self.charm), self.charm, "") |
1331 | - self.relation_state_manager = RelationStateManager(self.client) |
1332 | - |
1333 | - @inlineCallbacks |
1334 | - def add_service(self, name): |
1335 | - service_state = yield self.service_state_manager.add_service_state( |
1336 | - name, self.charm_state, dummy_constraints) |
1337 | - returnValue(service_state) |
1338 | - |
1339 | - @inlineCallbacks |
1340 | - def add_service_from_charm( |
1341 | - self, service_name, charm_id=None, constraints=None, |
1342 | - charm_dir=None, charm_name=None): |
1343 | - """Add a service from a charm. |
1344 | - """ |
1345 | - if not charm_id and charm_dir is None: |
1346 | - charm_name = charm_name or service_name |
1347 | - charm_dir = CharmDirectory(os.path.join( |
1348 | - test_repository_path, "series", charm_name)) |
1349 | - if charm_id is None: |
1350 | - charm_state = yield self.charm_state_manager.add_charm_state( |
1351 | - local_charm_id(charm_dir), charm_dir, "") |
1352 | - else: |
1353 | - charm_state = yield self.charm_state_manager.get_charm_state( |
1354 | - charm_id) |
1355 | - service_state = yield self.service_state_manager.add_service_state( |
1356 | - service_name, charm_state, constraints or dummy_constraints) |
1357 | - returnValue(service_state) |
1358 | - |
1359 | - @inlineCallbacks |
1360 | - def get_subordinate_charm(self): |
1361 | - """Return charm state for a subordinate charm. |
1362 | - |
1363 | - Many tests rely on adding relationships to a proper subordinate. |
1364 | - This return the charm state of a testing subordinate charm. |
1365 | - """ |
1366 | - unbundled_repo_path = self.makeDir() |
1367 | - os.rmdir(unbundled_repo_path) |
1368 | - shutil.copytree(unbundled_repository, unbundled_repo_path) |
1369 | - |
1370 | - sub_charm = CharmDirectory( |
1371 | - os.path.join(unbundled_repo_path, "series", "logging")) |
1372 | - self.charm_state_manager.add_charm_state("local:series/logging-1", |
1373 | - sub_charm, "") |
1374 | - logging_charm_state = yield self.charm_state_manager.get_charm_state( |
1375 | - "local:series/logging-1") |
1376 | - returnValue(logging_charm_state) |
1377 | - |
1378 | - @inlineCallbacks |
1379 | - def add_relation(self, relation_type, relation_scope, *services): |
1380 | - endpoints = [] |
1381 | - for service_meta in services: |
1382 | - service_state, relation_name, relation_role = service_meta |
1383 | - endpoints.append(RelationEndpoint( |
1384 | - service_state.service_name, |
1385 | - relation_type, |
1386 | - relation_name, |
1387 | - relation_role, |
1388 | - relation_scope)) |
1389 | - relation_state = yield self.relation_state_manager.add_relation_state( |
1390 | - *endpoints) |
1391 | - returnValue(relation_state[0]) |
1392 | - |
1393 | - @inlineCallbacks |
1394 | - def remove_service(self, internal_service_id): |
1395 | - topology = yield self.get_topology() |
1396 | - topology.remove_service(internal_service_id) |
1397 | - yield self.set_topology(topology) |
1398 | - |
1399 | - @inlineCallbacks |
1400 | - def remove_service_unit(self, internal_service_id, internal_unit_id): |
1401 | - topology = yield self.get_topology() |
1402 | - topology.remove_service_unit(internal_service_id, internal_unit_id) |
1403 | - yield self.set_topology(topology) |
1404 | - |
1405 | - def add_machine_state(self, constraints=None): |
1406 | - return self.machine_state_manager.add_machine_state( |
1407 | - constraints or series_constraints) |
1408 | - |
1409 | - @inlineCallbacks |
1410 | - def assert_machine_states(self, present, absent): |
1411 | - """Assert that machine IDs are either `present` or `absent` in topo.""" |
1412 | - for machine_id in present: |
1413 | - self.assertTrue( |
1414 | - (yield self.machine_state_manager.get_machine_state( |
1415 | - machine_id))) |
1416 | - for machine_id in absent: |
1417 | - ex = yield self.assertFailure( |
1418 | - self.machine_state_manager.get_machine_state(machine_id), |
1419 | - MachineStateNotFound) |
1420 | - self.assertEqual(ex.machine_id, machine_id) |
1421 | - |
1422 | - @inlineCallbacks |
1423 | - def assert_machine_assignments(self, service_name, machine_ids): |
1424 | - """Assert that `service_name` is deployed on `machine_ids`.""" |
1425 | - topology = yield self.get_topology() |
1426 | - service_id = topology.find_service_with_name(service_name) |
1427 | - assigned_machine_ids = [ |
1428 | - topology.get_service_unit_machine(service_id, unit_id) |
1429 | - for unit_id in topology.get_service_units(service_id)] |
1430 | - internal_machine_ids = [] |
1431 | - for machine_id in machine_ids: |
1432 | - if machine_id is None: |
1433 | - # corresponds to get_service_unit_machine API in this case |
1434 | - internal_machine_ids.append(None) |
1435 | - else: |
1436 | - internal_machine_ids.append("machine-%010d" % machine_id) |
1437 | - self.assertEqual( |
1438 | - set(assigned_machine_ids), set(internal_machine_ids)) |
1439 | - |
1440 | - @inlineCallbacks |
1441 | - def get_unit_state(self): |
1442 | - """Simple test helper to get a unit state.""" |
1443 | - service_state = yield self.service_state_manager.add_service_state( |
1444 | - "wordpress", self.charm_state, dummy_constraints) |
1445 | - unit_state = yield service_state.add_unit_state() |
1446 | - returnValue(unit_state) |
1447 | + @inlineCallbacks |
1448 | + def setUp(self): |
1449 | + yield super(ServiceStateManagerTestBase, self).setUp() |
1450 | + yield self.push_default_config() |
1451 | + self.charm_state_manager = CharmStateManager(self.client) |
1452 | + self.service_state_manager = ServiceStateManager(self.client) |
1453 | + self.machine_state_manager = MachineStateManager(self.client) |
1454 | + self.charm_state = yield self.charm_state_manager.add_charm_state( |
1455 | + local_charm_id(self.charm), self.charm, "") |
1456 | + self.relation_state_manager = RelationStateManager(self.client) |
1457 | + |
1458 | + @inlineCallbacks |
1459 | + def add_service(self, name): |
1460 | + service_state = yield self.service_state_manager.add_service_state( |
1461 | + name, self.charm_state, dummy_constraints) |
1462 | + returnValue(service_state) |
1463 | + |
1464 | + @inlineCallbacks |
1465 | + def add_service_from_charm( |
1466 | + self, service_name, charm_id=None, constraints=None, |
1467 | + charm_dir=None, charm_name=None): |
1468 | + """Add a service from a charm. |
1469 | + """ |
1470 | + if not charm_id and charm_dir is None: |
1471 | + charm_name = charm_name or service_name |
1472 | + charm_dir = CharmDirectory(os.path.join( |
1473 | + test_repository_path, "series", charm_name)) |
1474 | + if charm_id is None: |
1475 | + charm_state = yield self.charm_state_manager.add_charm_state( |
1476 | + local_charm_id(charm_dir), charm_dir, "") |
1477 | + else: |
1478 | + charm_state = yield self.charm_state_manager.get_charm_state( |
1479 | + charm_id) |
1480 | + service_state = yield self.service_state_manager.add_service_state( |
1481 | + service_name, charm_state, constraints or dummy_constraints) |
1482 | + returnValue(service_state) |
1483 | + |
1484 | + @inlineCallbacks |
1485 | + def get_subordinate_charm(self): |
1486 | + """Return charm state for a subordinate charm. |
1487 | + |
1488 | + Many tests rely on adding relationships to a proper subordinate. |
1489 | + This return the charm state of a testing subordinate charm. |
1490 | + """ |
1491 | + unbundled_repo_path = self.makeDir() |
1492 | + os.rmdir(unbundled_repo_path) |
1493 | + shutil.copytree(unbundled_repository, unbundled_repo_path) |
1494 | + |
1495 | + sub_charm = CharmDirectory( |
1496 | + os.path.join(unbundled_repo_path, "series", "logging")) |
1497 | + self.charm_state_manager.add_charm_state("local:series/logging-1", |
1498 | + sub_charm, "") |
1499 | + logging_charm_state = yield self.charm_state_manager.get_charm_state( |
1500 | + "local:series/logging-1") |
1501 | + returnValue(logging_charm_state) |
1502 | + |
1503 | + @inlineCallbacks |
1504 | + def add_relation(self, relation_type, relation_scope, *services): |
1505 | + endpoints = [] |
1506 | + for service_meta in services: |
1507 | + service_state, relation_name, relation_role = service_meta |
1508 | + endpoints.append(RelationEndpoint( |
1509 | + service_state.service_name, |
1510 | + relation_type, |
1511 | + relation_name, |
1512 | + relation_role, |
1513 | + relation_scope)) |
1514 | + relation_state = yield self.relation_state_manager.add_relation_state( |
1515 | + *endpoints) |
1516 | + returnValue(relation_state[0]) |
1517 | + |
1518 | + @inlineCallbacks |
1519 | + def remove_service(self, internal_service_id): |
1520 | + topology = yield self.get_topology() |
1521 | + topology.remove_service(internal_service_id) |
1522 | + yield self.set_topology(topology) |
1523 | + |
1524 | + @inlineCallbacks |
1525 | + def remove_service_unit(self, internal_service_id, internal_unit_id): |
1526 | + topology = yield self.get_topology() |
1527 | + topology.remove_service_unit(internal_service_id, internal_unit_id) |
1528 | + yield self.set_topology(topology) |
1529 | + |
1530 | + def add_machine_state(self, constraints=None): |
1531 | + return self.machine_state_manager.add_machine_state( |
1532 | + constraints or series_constraints) |
1533 | + |
1534 | + @inlineCallbacks |
1535 | + def assert_machine_states(self, present, absent): |
1536 | + """Assert that machine IDs are either `present` or `absent` in topo.""" |
1537 | + for machine_id in present: |
1538 | + self.assertTrue( |
1539 | + (yield self.machine_state_manager.get_machine_state( |
1540 | + machine_id))) |
1541 | + for machine_id in absent: |
1542 | + ex = yield self.assertFailure( |
1543 | + self.machine_state_manager.get_machine_state(machine_id), |
1544 | + MachineStateNotFound) |
1545 | + self.assertEqual(ex.machine_id, machine_id) |
1546 | + |
1547 | + @inlineCallbacks |
1548 | + def assert_machine_assignments(self, service_name, machine_ids): |
1549 | + """Assert that `service_name` is deployed on `machine_ids`.""" |
1550 | + topology = yield self.get_topology() |
1551 | + service_id = topology.find_service_with_name(service_name) |
1552 | + assigned_machine_ids = [ |
1553 | + topology.get_service_unit_machine(service_id, unit_id) |
1554 | + for unit_id in topology.get_service_units(service_id)] |
1555 | + internal_machine_ids = [] |
1556 | + for machine_id in machine_ids: |
1557 | + if machine_id is None: |
1558 | + # corresponds to get_service_unit_machine API in this case |
1559 | + internal_machine_ids.append(None) |
1560 | + else: |
1561 | + internal_machine_ids.append("machine-%010d" % machine_id) |
1562 | + self.assertEqual( |
1563 | + set(assigned_machine_ids), set(internal_machine_ids)) |
1564 | + |
1565 | + @inlineCallbacks |
1566 | + def get_unit_state(self): |
1567 | + """Simple test helper to get a unit state.""" |
1568 | + service_state = yield self.service_state_manager.add_service_state( |
1569 | + "wordpress", self.charm_state, dummy_constraints) |
1570 | + unit_state = yield service_state.add_unit_state() |
1571 | + returnValue(unit_state) |
1572 | |
1573 | |
1574 | class ServiceStateManagerTest(ServiceStateManagerTestBase): |
1575 | |
1576 | - @inlineCallbacks |
1577 | - def test_add_service(self): |
1578 | - """ |
1579 | - Adding a service state should register it in zookeeper, |
1580 | - including the requested charm id. |
1581 | - """ |
1582 | - yield self.service_state_manager.add_service_state( |
1583 | - "wordpress", self.charm_state, dummy_constraints) |
1584 | - yield self.service_state_manager.add_service_state( |
1585 | - "mysql", self.charm_state, dummy_constraints) |
1586 | - children = yield self.client.get_children("/services") |
1587 | - |
1588 | - self.assertEquals(sorted(children), |
1589 | - ["service-0000000000", "service-0000000001"]) |
1590 | - |
1591 | - content, stat = yield self.client.get("/services/service-0000000000") |
1592 | - details = yaml.load(content) |
1593 | - self.assertTrue(details) |
1594 | - self.assertEquals(details.get("charm"), "local:series/dummy-1") |
1595 | - self.assertFalse(isinstance(details.get("charm"), unicode)) |
1596 | - self.assertEquals(details.get("constraints"), series_constraints.data) |
1597 | - |
1598 | - topology = yield self.get_topology() |
1599 | - self.assertEquals(topology.find_service_with_name("wordpress"), |
1600 | - "service-0000000000") |
1601 | - self.assertEquals(topology.find_service_with_name("mysql"), |
1602 | - "service-0000000001") |
1603 | - |
1604 | - @inlineCallbacks |
1605 | - def test_add_service_with_duplicated_name(self): |
1606 | - """ |
1607 | - If a service is added with a duplicated name, a meaningful |
1608 | - error should be raised. |
1609 | - """ |
1610 | - yield self.service_state_manager.add_service_state( |
1611 | - "wordpress", self.charm_state, dummy_constraints) |
1612 | - |
1613 | - try: |
1614 | - yield self.service_state_manager.add_service_state( |
1615 | - "wordpress", self.charm_state, dummy_constraints) |
1616 | - except ServiceStateNameInUse, e: |
1617 | - self.assertEquals(e.service_name, "wordpress") |
1618 | - else: |
1619 | - self.fail("Error not raised") |
1620 | - |
1621 | - @inlineCallbacks |
1622 | - def test_get_service_and_check_attributes(self): |
1623 | - """ |
1624 | - Getting a service state should be possible, and the service |
1625 | - state identification should be available through its |
1626 | - attributes. |
1627 | - """ |
1628 | - yield self.service_state_manager.add_service_state( |
1629 | - "wordpress", self.charm_state, dummy_constraints) |
1630 | - service_state = yield self.service_state_manager.get_service_state( |
1631 | - "wordpress") |
1632 | - self.assertEquals(service_state.service_name, "wordpress") |
1633 | - self.assertEquals(service_state.internal_id, "service-0000000000") |
1634 | - |
1635 | - @inlineCallbacks |
1636 | - def test_get_service_not_found(self): |
1637 | - """ |
1638 | - Getting a service state which is not available should errback |
1639 | - a meaningful error. |
1640 | - """ |
1641 | - try: |
1642 | - yield self.service_state_manager.get_service_state("wordpress") |
1643 | - except ServiceStateNotFound, e: |
1644 | - self.assertEquals(e.service_name, "wordpress") |
1645 | - else: |
1646 | - self.fail("Error not raised") |
1647 | - |
1648 | - def test_get_unit_state(self): |
1649 | - """A unit state can be retrieved by name from the service manager.""" |
1650 | - self.assertFailure(self.service_state_manager.get_unit_state( |
1651 | - "wordpress/1"), ServiceStateNotFound) |
1652 | - |
1653 | - self.assertFailure(self.service_state_manager.get_unit_state( |
1654 | - "wordpress1"), ServiceUnitStateNotFound) |
1655 | - |
1656 | - wordpress_state = yield self.service_state_manager.add_service_state( |
1657 | - "wordpress", self.charm_state, dummy_constraints) |
1658 | - |
1659 | - self.assertFailure(self.service_state_manager.get_unit_state( |
1660 | - "wordpress/1"), ServiceUnitStateNotFound) |
1661 | - |
1662 | - wordpress_unit = wordpress_state.add_unit_state() |
1663 | - |
1664 | - unit_state = yield self.service_state_manager.get_unit_state( |
1665 | - "wordpress/1") |
1666 | - |
1667 | - self.assertEqual(unit_state.internal_id, wordpress_unit.internal_id) |
1668 | - |
1669 | - @inlineCallbacks |
1670 | - def test_get_service_charm_id(self): |
1671 | - """ |
1672 | - The service state should make its respective charm id available. |
1673 | - """ |
1674 | - yield self.service_state_manager.add_service_state( |
1675 | - "wordpress", self.charm_state, dummy_constraints) |
1676 | - service_state = yield self.service_state_manager.get_service_state( |
1677 | - "wordpress") |
1678 | - charm_id = yield service_state.get_charm_id() |
1679 | - self.assertEquals(charm_id, "local:series/dummy-1") |
1680 | - |
1681 | - @inlineCallbacks |
1682 | - def test_set_service_charm_id(self): |
1683 | - """ |
1684 | - The service state should allow its charm id to be set. |
1685 | - """ |
1686 | - yield self.service_state_manager.add_service_state( |
1687 | - "wordpress", self.charm_state, dummy_constraints) |
1688 | - service_state = yield self.service_state_manager.get_service_state( |
1689 | - "wordpress") |
1690 | - yield service_state.set_charm_id("local:series/dummy-2") |
1691 | - charm_id = yield service_state.get_charm_id() |
1692 | - self.assertEquals(charm_id, "local:series/dummy-2") |
1693 | - |
1694 | - @inlineCallbacks |
1695 | - def test_get_service_constraints(self): |
1696 | - """The service state should make constraints available""" |
1697 | - initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"]) |
1698 | - yield self.service_state_manager.add_service_state( |
1699 | - "wordpress", self.charm_state, initial_constraints) |
1700 | - service_state = yield self.service_state_manager.get_service_state( |
1701 | - "wordpress") |
1702 | - constraints = yield service_state.get_constraints() |
1703 | - self.assertEquals( |
1704 | - constraints, initial_constraints.with_series("series")) |
1705 | - |
1706 | - @inlineCallbacks |
1707 | - def test_get_service_constraints_inherits(self): |
1708 | - """The service constraints should be combined with the environment's""" |
1709 | - yield self.push_env_constraints("arch=arm", "cpu=32") |
1710 | - service_constraints = dummy_cs.parse(["cpu=256"]) |
1711 | - yield self.service_state_manager.add_service_state( |
1712 | - "wordpress", self.charm_state, service_constraints) |
1713 | - service_state = yield self.service_state_manager.get_service_state( |
1714 | - "wordpress") |
1715 | - constraints = yield service_state.get_constraints() |
1716 | - expected_base = dummy_cs.parse(["arch=arm", "cpu=256"]) |
1717 | - self.assertEquals(constraints, expected_base.with_series("series")) |
1718 | - |
1719 | - @inlineCallbacks |
1720 | - def test_get_missing_service_constraints(self): |
1721 | - """ |
1722 | - Nodes created before the constraints mechanism was added should have |
1723 | - empty constraints. |
1724 | - """ |
1725 | - yield self.client.delete("/constraints") |
1726 | - yield self.service_state_manager.add_service_state( |
1727 | - "wordpress", self.charm_state, dummy_constraints) |
1728 | - service_state = yield self.service_state_manager.get_service_state( |
1729 | - "wordpress") |
1730 | - path = "/services/" + service_state.internal_id |
1731 | - node = YAMLState(self.client, path) |
1732 | - yield node.read() |
1733 | - del node["constraints"] |
1734 | - yield node.write() |
1735 | - constraints = yield service_state.get_constraints() |
1736 | - self.assertEquals(constraints.data, {}) |
1737 | - |
1738 | - @inlineCallbacks |
1739 | - def test_get_missing_unit_constraints(self): |
1740 | - """ |
1741 | - Nodes created before the constraints mechanism was added should have |
1742 | - empty constraints. |
1743 | - """ |
1744 | - yield self.service_state_manager.add_service_state( |
1745 | - "wordpress", self.charm_state, dummy_constraints) |
1746 | - service_state = yield self.service_state_manager.get_service_state( |
1747 | - "wordpress") |
1748 | - unit_state = yield service_state.add_unit_state() |
1749 | - path = "/units/" + unit_state.internal_id |
1750 | - node = YAMLState(self.client, path) |
1751 | - yield node.read() |
1752 | - del node["constraints"] |
1753 | - yield node.write() |
1754 | - constraints = yield unit_state.get_constraints() |
1755 | - self.assertEquals(constraints.data, {}) |
1756 | - |
1757 | - @inlineCallbacks |
1758 | - def test_set_service_constraints(self): |
1759 | - """The service state should make constraints available for change""" |
1760 | - initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"]) |
1761 | - yield self.service_state_manager.add_service_state( |
1762 | - "wordpress", self.charm_state, initial_constraints) |
1763 | - service_state = yield self.service_state_manager.get_service_state( |
1764 | - "wordpress") |
1765 | - new_constraints = dummy_cs.parse(["mem=2G", "arch=arm"]) |
1766 | - yield service_state.set_constraints(new_constraints) |
1767 | - retrieved_constraints = yield service_state.get_constraints() |
1768 | - self.assertEquals( |
1769 | - retrieved_constraints, new_constraints.with_series("series")) |
1770 | - |
1771 | - @inlineCallbacks |
1772 | - def test_remove_service_state(self): |
1773 | - """ |
1774 | - A service state can be removed along with its relations, units, |
1775 | - and zookeeper state. |
1776 | - """ |
1777 | - service_state = yield self.service_state_manager.add_service_state( |
1778 | - "wordpress", self.charm_state, dummy_constraints) |
1779 | - |
1780 | - relation_state = yield self.add_relation( |
1781 | - "rel-type2", "global", [service_state, "app", "server"]) |
1782 | - |
1783 | - unit_state = yield service_state.add_unit_state() |
1784 | - machine_state = yield self.machine_state_manager.add_machine_state( |
1785 | - series_constraints) |
1786 | - yield unit_state.assign_to_machine(machine_state) |
1787 | - |
1788 | - yield self.service_state_manager.remove_service_state(service_state) |
1789 | - |
1790 | - topology = yield self.get_topology() |
1791 | - self.assertFalse(topology.has_relation(relation_state.internal_id)) |
1792 | - self.assertFalse(topology.has_service(service_state.internal_id)) |
1793 | - self.assertFalse( |
1794 | - topology.get_service_units_in_machine(machine_state.internal_id)) |
1795 | - |
1796 | - exists = yield self.client.exists( |
1797 | - "/services/%s" % service_state.internal_id) |
1798 | - self.assertFalse(exists) |
1799 | - |
1800 | - @inlineCallbacks |
1801 | - def test_add_service_unit_and_check_attributes(self): |
1802 | - """ |
1803 | - A service state should enable adding a new service unit |
1804 | - under it, and again the unit should offer attributes allowing |
1805 | - its identification. |
1806 | - """ |
1807 | - service_state0 = yield self.service_state_manager.add_service_state( |
1808 | - "wordpress", self.charm_state, dummy_constraints) |
1809 | - service_state1 = yield self.service_state_manager.add_service_state( |
1810 | - "mysql", self.charm_state, dummy_constraints) |
1811 | - |
1812 | - unit_state0 = yield service_state0.add_unit_state() |
1813 | - unit_state1 = yield service_state1.add_unit_state() |
1814 | - unit_state2 = yield service_state0.add_unit_state() |
1815 | - unit_state3 = yield service_state1.add_unit_state() |
1816 | - |
1817 | - children = yield self.client.get_children("/units") |
1818 | - self.assertEquals(sorted(children), |
1819 | - ["unit-0000000000", "unit-0000000001", |
1820 | - "unit-0000000002", "unit-0000000003"]) |
1821 | - |
1822 | - self.assertEquals(unit_state0.service_name, "wordpress") |
1823 | - self.assertEquals(unit_state0.internal_id, "unit-0000000000") |
1824 | - self.assertEquals(unit_state0.unit_name, "wordpress/0") |
1825 | - |
1826 | - self.assertEquals(unit_state1.service_name, "mysql") |
1827 | - self.assertEquals(unit_state1.internal_id, "unit-0000000001") |
1828 | - self.assertEquals(unit_state1.unit_name, "mysql/0") |
1829 | - |
1830 | - self.assertEquals(unit_state2.service_name, "wordpress") |
1831 | - self.assertEquals(unit_state2.internal_id, "unit-0000000002") |
1832 | - self.assertEquals(unit_state2.unit_name, "wordpress/1") |
1833 | - |
1834 | - self.assertEquals(unit_state3.service_name, "mysql") |
1835 | - self.assertEquals(unit_state3.internal_id, "unit-0000000003") |
1836 | - self.assertEquals(unit_state3.unit_name, "mysql/1") |
1837 | - |
1838 | - topology = yield self.get_topology() |
1839 | - |
1840 | - self.assertTrue( |
1841 | - topology.has_service_unit("service-0000000000", |
1842 | - "unit-0000000000")) |
1843 | - self.assertTrue( |
1844 | - topology.has_service_unit("service-0000000001", |
1845 | - "unit-0000000001")) |
1846 | - self.assertTrue( |
1847 | - topology.has_service_unit("service-0000000000", |
1848 | - "unit-0000000002")) |
1849 | - self.assertTrue( |
1850 | - topology.has_service_unit("service-0000000001", |
1851 | - "unit-0000000003")) |
1852 | - |
1853 | - def get_presence_path( |
1854 | - self, relation_state, relation_role, unit_state, container=None): |
1855 | - container = container.internal_id if container else None |
1856 | - presence_path = "/".join(filter(None, [ |
1857 | - "/relations", |
1858 | - relation_state.internal_id, |
1859 | - container, |
1860 | - relation_role, |
1861 | - unit_state.internal_id])) |
1862 | - return presence_path |
1863 | - |
1864 | - @inlineCallbacks |
1865 | - def test_add_service_unit_with_container(self): |
1866 | - """ |
1867 | - Validate adding units with containers specified and recovering that. |
1868 | - """ |
1869 | - mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info", |
1870 | - "server", "global") |
1871 | - logging_ep = RelationEndpoint("logging", "juju-info", "juju-info", |
1872 | - "client", "container") |
1873 | - |
1874 | - logging_charm_state = yield self.get_subordinate_charm() |
1875 | - self.assertTrue(logging_charm_state.is_subordinate()) |
1876 | - log_state = yield self.service_state_manager.add_service_state( |
1877 | - "logging", logging_charm_state, dummy_constraints) |
1878 | - mysql_state = yield self.service_state_manager.add_service_state( |
1879 | - "mysql", self.charm_state, dummy_constraints) |
1880 | - |
1881 | - relation_state, service_states = (yield |
1882 | - self.relation_state_manager.add_relation_state( |
1883 | - mysql_ep, logging_ep)) |
1884 | - |
1885 | - unit_state1 = yield mysql_state.add_unit_state() |
1886 | - unit_state0 = yield log_state.add_unit_state(container=unit_state1) |
1887 | - |
1888 | - unit_state3 = yield mysql_state.add_unit_state() |
1889 | - unit_state2 = yield log_state.add_unit_state(container=unit_state3) |
1890 | - |
1891 | - self.assertEquals((yield unit_state1.get_container()), None) |
1892 | - self.assertEquals((yield unit_state0.get_container()), unit_state1) |
1893 | - self.assertEquals((yield unit_state2.get_container()), unit_state3) |
1894 | - self.assertEquals((yield unit_state3.get_container()), None) |
1895 | - |
1896 | - for unit_state in (unit_state1, unit_state3): |
1897 | - yield unit_state.set_private_address( |
1898 | - "%s.example.com" % ( |
1899 | - unit_state.unit_name.replace("/", "-"))) |
1900 | - |
1901 | - # construct the proper relation state |
1902 | - mystate = pick_attr(service_states, relation_role="server") |
1903 | - logstate = pick_attr(service_states, relation_role="client") |
1904 | - yield logstate.add_unit_state(unit_state0) |
1905 | - yield logstate.add_unit_state(unit_state2) |
1906 | - |
1907 | - yield mystate.add_unit_state(unit_state1) |
1908 | - yield mystate.add_unit_state(unit_state3) |
1909 | - |
1910 | - @inlineCallbacks |
1911 | - def verify_container(relation_state, service_relation_state, |
1912 | - unit_state, container): |
1913 | - presence_path = self.get_presence_path( |
1914 | - relation_state, |
1915 | - service_relation_state.relation_role, |
1916 | - unit_state, |
1917 | - container) |
1918 | - |
1919 | - content, stat = yield self.client.get(presence_path) |
1920 | - self.assertTrue(stat) |
1921 | - self.assertEqual(content, '') |
1922 | - # verify the node data on the relation role nodes |
1923 | - role_path = os.path.dirname(presence_path) |
1924 | - # role path |
1925 | - content, stat = yield self.client.get(role_path) |
1926 | - self.assertTrue(stat) |
1927 | - node_info = yaml.load(content) |
1928 | - |
1929 | - self.assertEqual( |
1930 | - node_info["name"], |
1931 | - service_relation_state.relation_name) |
1932 | - self.assertEqual( |
1933 | - node_info["role"], |
1934 | - service_relation_state.relation_role) |
1935 | - |
1936 | - settings_path = os.path.dirname( |
1937 | - os.path.dirname(presence_path)) + "/settings/" + \ |
1938 | - unit_state.internal_id |
1939 | - content, stat = yield self.client.get(settings_path) |
1940 | - self.assertTrue(stat) |
1941 | - settings_info = yaml.load(content) |
1942 | - |
1943 | - # Verify that private address was set |
1944 | - # we verify the content elsewhere |
1945 | - self.assertTrue(settings_info["private-address"]) |
1946 | - |
1947 | - # verify all the units are constructed as expected |
1948 | - # first the client roles with another container |
1949 | - yield verify_container(relation_state, logstate, |
1950 | - unit_state0, unit_state1) |
1951 | - |
1952 | - yield verify_container(relation_state, logstate, |
1953 | - unit_state2, unit_state3) |
1954 | - |
1955 | - # and now the principals (which are their own relation containers) |
1956 | - yield verify_container(relation_state, logstate, |
1957 | - unit_state0, unit_state1) |
1958 | - |
1959 | - yield verify_container(relation_state, mystate, |
1960 | - unit_state1, unit_state1) |
1961 | - |
1962 | - @inlineCallbacks |
1963 | - def test_add_service_unit_with_changing_state(self): |
1964 | - """ |
1965 | - When adding a service unit, there's a chance that the |
1966 | - service will go away mid-way through. Rather than blowing |
1967 | - up randomly, a nice error should be raised. |
1968 | - """ |
1969 | - service_state = yield self.service_state_manager.add_service_state( |
1970 | - "wordpress", self.charm_state, dummy_constraints) |
1971 | - |
1972 | - yield self.remove_service(service_state.internal_id) |
1973 | - |
1974 | - d = service_state.add_unit_state() |
1975 | - yield self.assertFailure(d, StateChanged) |
1976 | - |
1977 | - @inlineCallbacks |
1978 | - def test_get_unit_names(self): |
1979 | - """A service's units names are retrievable.""" |
1980 | - service_state = yield self.service_state_manager.add_service_state( |
1981 | - "wordpress", self.charm_state, dummy_constraints) |
1982 | - |
1983 | - expected_names = [] |
1984 | - for i in range(3): |
1985 | - unit_state = yield service_state.add_unit_state() |
1986 | - expected_names.append(unit_state.unit_name) |
1987 | - |
1988 | - unit_names = yield service_state.get_unit_names() |
1989 | - self.assertEqual(unit_names, expected_names) |
1990 | - |
1991 | - @inlineCallbacks |
1992 | - def test_remove_service_unit(self): |
1993 | - """Removing a service unit removes all state associated. |
1994 | - """ |
1995 | - service_state = yield self.service_state_manager.add_service_state( |
1996 | - "wordpress", self.charm_state, dummy_constraints) |
1997 | - |
1998 | - unit_state = yield service_state.add_unit_state() |
1999 | - |
2000 | - # Assign to a machine |
2001 | - machine_state = yield self.machine_state_manager.add_machine_state( |
2002 | - series_constraints) |
2003 | - yield unit_state.assign_to_machine(machine_state) |
2004 | - # Connect a unit agent |
2005 | - yield unit_state.connect_agent() |
2006 | - |
2007 | - # Now try and destroy it. |
2008 | - yield service_state.remove_unit_state(unit_state) |
2009 | - |
2010 | - # Verify destruction. |
2011 | - topology = yield self.get_topology() |
2012 | - self.assertTrue(topology.has_service(service_state.internal_id)) |
2013 | - self.assertFalse( |
2014 | - topology.has_service_unit( |
2015 | - service_state.internal_id, unit_state.internal_id)) |
2016 | - |
2017 | - exists = yield self.client.exists("/units/%s" % unit_state.internal_id) |
2018 | - self.assertFalse(exists) |
2019 | - |
2020 | - def test_remove_service_unit_nonexistant(self): |
2021 | - """Removing a non existant service unit, is fine.""" |
2022 | - |
2023 | - service_state = yield self.service_state_manager.add_service_state( |
2024 | - "wordpress", self.charm_state, dummy_constraints) |
2025 | - unit_state = yield service_state.add_unit_state() |
2026 | - yield service_state.remove_unit_state(unit_state) |
2027 | - yield service_state.remove_unit_state(unit_state) |
2028 | - |
2029 | - @inlineCallbacks |
2030 | - def test_get_all_service_states(self): |
2031 | - services = yield self.service_state_manager.get_all_service_states() |
2032 | - self.assertFalse(services) |
2033 | - |
2034 | - yield self.service_state_manager.add_service_state( |
2035 | - "wordpress", self.charm_state, dummy_constraints) |
2036 | - services = yield self.service_state_manager.get_all_service_states() |
2037 | - self.assertEquals(len(services), 1) |
2038 | - |
2039 | - yield self.service_state_manager.add_service_state( |
2040 | - "mysql", self.charm_state, dummy_constraints) |
2041 | - services = yield self.service_state_manager.get_all_service_states() |
2042 | - self.assertEquals(len(services), 2) |
2043 | - |
2044 | - @inlineCallbacks |
2045 | - def test_get_service_unit(self): |
2046 | - """ |
2047 | - Getting back service units should be possible using the |
2048 | - user-oriented id. |
2049 | - """ |
2050 | - service_state0 = yield self.service_state_manager.add_service_state( |
2051 | - "wordpress", self.charm_state, dummy_constraints) |
2052 | - service_state1 = yield self.service_state_manager.add_service_state( |
2053 | - "mysql", self.charm_state, dummy_constraints) |
2054 | - |
2055 | - yield service_state0.add_unit_state() |
2056 | - yield service_state1.add_unit_state() |
2057 | - yield service_state0.add_unit_state() |
2058 | - yield service_state1.add_unit_state() |
2059 | - |
2060 | - unit_state0 = yield service_state0.get_unit_state("wordpress/0") |
2061 | - unit_state1 = yield service_state1.get_unit_state("mysql/0") |
2062 | - unit_state2 = yield service_state0.get_unit_state("wordpress/1") |
2063 | - unit_state3 = yield service_state1.get_unit_state("mysql/1") |
2064 | - |
2065 | - self.assertEquals(unit_state0.internal_id, "unit-0000000000") |
2066 | - self.assertEquals(unit_state1.internal_id, "unit-0000000001") |
2067 | - self.assertEquals(unit_state2.internal_id, "unit-0000000002") |
2068 | - self.assertEquals(unit_state3.internal_id, "unit-0000000003") |
2069 | - |
2070 | - self.assertEquals(unit_state0.unit_name, "wordpress/0") |
2071 | - self.assertEquals(unit_state1.unit_name, "mysql/0") |
2072 | - self.assertEquals(unit_state2.unit_name, "wordpress/1") |
2073 | - self.assertEquals(unit_state3.unit_name, "mysql/1") |
2074 | - |
2075 | - @inlineCallbacks |
2076 | - def test_get_all_unit_states(self): |
2077 | - service_state0 = yield self.service_state_manager.add_service_state( |
2078 | - "wordpress", self.charm_state, dummy_constraints) |
2079 | - service_state1 = yield self.service_state_manager.add_service_state( |
2080 | - "mysql", self.charm_state, dummy_constraints) |
2081 | - |
2082 | - yield service_state0.add_unit_state() |
2083 | - yield service_state1.add_unit_state() |
2084 | - yield service_state0.add_unit_state() |
2085 | - yield service_state1.add_unit_state() |
2086 | - |
2087 | - unit_state0 = yield service_state0.get_unit_state("wordpress/0") |
2088 | - unit_state1 = yield service_state1.get_unit_state("mysql/0") |
2089 | - unit_state2 = yield service_state0.get_unit_state("wordpress/1") |
2090 | - unit_state3 = yield service_state1.get_unit_state("mysql/1") |
2091 | - |
2092 | - wordpress_units = yield service_state0.get_all_unit_states() |
2093 | - self.assertEquals( |
2094 | - set(wordpress_units), set((unit_state0, unit_state2))) |
2095 | - |
2096 | - mysql_units = yield service_state1.get_all_unit_states() |
2097 | - self.assertEquals(set(mysql_units), set((unit_state1, unit_state3))) |
2098 | - |
2099 | - @inlineCallbacks |
2100 | - def test_get_all_unit_states_with_changing_state(self): |
2101 | - """ |
2102 | - When getting the service unit states, there's a chance that |
2103 | - the service will go away mid-way through. Rather than blowing |
2104 | - up randomly, a nice error should be raised. |
2105 | - """ |
2106 | - service_state = yield self.service_state_manager.add_service_state( |
2107 | - "wordpress", self.charm_state, dummy_constraints) |
2108 | - yield service_state.add_unit_state() |
2109 | - unit_state = (yield service_state.get_all_unit_states())[0] |
2110 | - self.assertEqual(unit_state.unit_name, "wordpress/0") |
2111 | - yield self.remove_service(service_state.internal_id) |
2112 | - yield self.assertFailure( |
2113 | - service_state.get_all_unit_states(), StateChanged) |
2114 | - |
2115 | - @inlineCallbacks |
2116 | - def test_set_functions(self): |
2117 | - wordpress = yield self.service_state_manager.add_service_state( |
2118 | - "wordpress", self.charm_state, dummy_constraints) |
2119 | - mysql = yield self.service_state_manager.add_service_state( |
2120 | - "mysql", self.charm_state, dummy_constraints) |
2121 | - |
2122 | - s1 = yield self.service_state_manager.get_service_state( |
2123 | - "wordpress") |
2124 | - s2 = yield self.service_state_manager.get_service_state( |
2125 | - "mysql") |
2126 | - self.assertEquals(hash(s1), hash(wordpress)) |
2127 | - self.assertEquals(hash(s2), hash(mysql)) |
2128 | - |
2129 | - self.assertNotEqual(s1, object()) |
2130 | - self.assertNotEqual(s1, s2) |
2131 | - |
2132 | - self.assertEquals(s1, wordpress) |
2133 | - self.assertEquals(s2, mysql) |
2134 | - |
2135 | - us0 = yield wordpress.add_unit_state() |
2136 | - us1 = yield wordpress.add_unit_state() |
2137 | - |
2138 | - unit_state0 = yield wordpress.get_unit_state("wordpress/0") |
2139 | - unit_state1 = yield wordpress.get_unit_state("wordpress/1") |
2140 | - |
2141 | - self.assertEquals(us0, unit_state0) |
2142 | - self.assertEquals(us1, unit_state1) |
2143 | - self.assertEquals(hash(us1), hash(unit_state1)) |
2144 | - |
2145 | - self.assertNotEqual(us0, object()) |
2146 | - self.assertNotEqual(us0, us1) |
2147 | - |
2148 | - @inlineCallbacks |
2149 | - def test_get_service_unit_not_found(self): |
2150 | - """ |
2151 | - Attempting to retrieve a non-existent service unit should |
2152 | - result in an errback. |
2153 | - """ |
2154 | - service_state0 = yield self.service_state_manager.add_service_state( |
2155 | - "wordpress", self.charm_state, dummy_constraints) |
2156 | - service_state1 = yield self.service_state_manager.add_service_state( |
2157 | - "mysql", self.charm_state, dummy_constraints) |
2158 | - |
2159 | - # Add some state in a different service to make it a little |
2160 | - # bit more prone to breaking in case of errors. |
2161 | - yield service_state1.add_unit_state() |
2162 | - |
2163 | - try: |
2164 | - yield service_state0.get_unit_state("wordpress/0") |
2165 | - except ServiceUnitStateNotFound, e: |
2166 | - self.assertEquals(e.unit_name, "wordpress/0") |
2167 | - else: |
2168 | - self.fail("Error not raised") |
2169 | - |
2170 | - @inlineCallbacks |
2171 | - def test_get_set_public_address(self): |
2172 | - service_state = yield self.service_state_manager.add_service_state( |
2173 | - "wordpress", self.charm_state, dummy_constraints) |
2174 | - unit_state = yield service_state.add_unit_state() |
2175 | - self.assertEqual((yield unit_state.get_public_address()), None) |
2176 | - yield unit_state.set_public_address("example.foobar.com") |
2177 | - yield self.assertEqual( |
2178 | - (yield unit_state.get_public_address()), |
2179 | - "example.foobar.com") |
2180 | - |
2181 | - @inlineCallbacks |
2182 | - def test_get_set_private_address(self): |
2183 | - service_state = yield self.service_state_manager.add_service_state( |
2184 | - "wordpress", self.charm_state, dummy_constraints) |
2185 | - unit_state = yield service_state.add_unit_state() |
2186 | - self.assertEqual((yield unit_state.get_private_address()), None) |
2187 | - yield unit_state.set_private_address("example.local") |
2188 | - yield self.assertEqual( |
2189 | - (yield unit_state.get_private_address()), |
2190 | - "example.local") |
2191 | - |
2192 | - @inlineCallbacks |
2193 | - def test_get_service_unit_with_changing_state(self): |
2194 | - """ |
2195 | - If a service is removed during operation, get_service_unit() |
2196 | - should raise a nice error. |
2197 | - """ |
2198 | - service_state = yield self.service_state_manager.add_service_state( |
2199 | - "wordpress", self.charm_state, dummy_constraints) |
2200 | - |
2201 | - yield self.remove_service(service_state.internal_id) |
2202 | - |
2203 | - d = service_state.get_unit_state("wordpress/0") |
2204 | - yield self.assertFailure(d, StateChanged) |
2205 | - |
2206 | - @inlineCallbacks |
2207 | - def test_get_service_unit_with_bad_service_name(self): |
2208 | - """ |
2209 | - Service unit names contain a service name embedded into |
2210 | - them. The service name requested when calling get_unit_state() |
2211 | - must match that of the object being used. |
2212 | - """ |
2213 | - service_state0 = yield self.service_state_manager.add_service_state( |
2214 | - "wordpress", self.charm_state, dummy_constraints) |
2215 | - service_state1 = yield self.service_state_manager.add_service_state( |
2216 | - "mysql", self.charm_state, dummy_constraints) |
2217 | - |
2218 | - # Add some state in a different service to make it a little |
2219 | - # bit more prone to breaking in case of errors. |
2220 | - yield service_state1.add_unit_state() |
2221 | - |
2222 | - try: |
2223 | - yield service_state0.get_unit_state("mysql/0") |
2224 | - except BadServiceStateName, e: |
2225 | - self.assertEquals(e.expected_name, "wordpress") |
2226 | - self.assertEquals(e.obtained_name, "mysql") |
2227 | - else: |
2228 | - self.fail("Error not raised") |
2229 | - |
2230 | - @inlineCallbacks |
2231 | - def test_assign_unit_to_machine(self): |
2232 | - service_state = yield self.service_state_manager.add_service_state( |
2233 | - "wordpress", self.charm_state, dummy_constraints) |
2234 | - unit_state = yield service_state.add_unit_state() |
2235 | - machine_state = yield self.machine_state_manager.add_machine_state( |
2236 | - series_constraints) |
2237 | - |
2238 | - yield unit_state.assign_to_machine(machine_state) |
2239 | - |
2240 | - topology = yield self.get_topology() |
2241 | - |
2242 | - self.assertEquals( |
2243 | - topology.get_service_unit_machine(service_state.internal_id, |
2244 | - unit_state.internal_id), |
2245 | - machine_state.internal_id) |
2246 | - |
2247 | - @inlineCallbacks |
2248 | - def test_assign_unit_to_machine_with_changing_state(self): |
2249 | - service_state = yield self.service_state_manager.add_service_state( |
2250 | - "wordpress", self.charm_state, dummy_constraints) |
2251 | - unit_state = yield service_state.add_unit_state() |
2252 | - machine_state = yield self.machine_state_manager.add_machine_state( |
2253 | - series_constraints) |
2254 | - |
2255 | - yield self.remove_service_unit(service_state.internal_id, |
2256 | - unit_state.internal_id) |
2257 | - |
2258 | - d = unit_state.assign_to_machine(machine_state) |
2259 | - yield self.assertFailure(d, StateChanged) |
2260 | - |
2261 | - yield self.remove_service(service_state.internal_id) |
2262 | - |
2263 | - d = unit_state.assign_to_machine(machine_state) |
2264 | - yield self.assertFailure(d, StateChanged) |
2265 | - |
2266 | - @inlineCallbacks |
2267 | - def test_unassign_unit_from_machine(self): |
2268 | - service_state = yield self.service_state_manager.add_service_state( |
2269 | - "wordpress", self.charm_state, dummy_constraints) |
2270 | - unit_state = yield service_state.add_unit_state() |
2271 | - machine_state = yield self.machine_state_manager.add_machine_state( |
2272 | - series_constraints) |
2273 | - |
2274 | - yield unit_state.assign_to_machine(machine_state) |
2275 | - yield unit_state.unassign_from_machine() |
2276 | - |
2277 | - topology = yield self.get_topology() |
2278 | - |
2279 | - self.assertEquals( |
2280 | - topology.get_service_unit_machine(service_state.internal_id, |
2281 | - unit_state.internal_id), |
2282 | - None) |
2283 | - |
2284 | - @inlineCallbacks |
2285 | - def test_get_set_clear_resolved(self): |
2286 | - """The a unit can be set to resolved to mark a future transition, with |
2287 | - an optional retry flag.""" |
2288 | - |
2289 | - unit_state = yield self.get_unit_state() |
2290 | - |
2291 | - self.assertIdentical((yield unit_state.get_resolved()), None) |
2292 | - yield unit_state.set_resolved(NO_HOOKS) |
2293 | - |
2294 | - yield self.assertFailure( |
2295 | - unit_state.set_resolved(NO_HOOKS), |
2296 | - ServiceUnitResolvedAlreadyEnabled) |
2297 | - yield self.assertEqual( |
2298 | - |
2299 | - (yield unit_state.get_resolved()), {"retry": NO_HOOKS}) |
2300 | - |
2301 | - yield unit_state.clear_resolved() |
2302 | - self.assertIdentical((yield unit_state.get_resolved()), None) |
2303 | - yield unit_state.clear_resolved() |
2304 | - |
2305 | - yield self.assertFailure(unit_state.set_resolved(None), ValueError) |
2306 | - |
2307 | - @inlineCallbacks |
2308 | - def test_watch_resolved(self): |
2309 | - """A unit resolved watch can be instituted on a permanent basis.""" |
2310 | - unit_state = yield self.get_unit_state() |
2311 | - |
2312 | - results = [] |
2313 | - |
2314 | - def callback(value): |
2315 | - results.append(value) |
2316 | - |
2317 | - unit_state.watch_resolved(callback) |
2318 | - yield unit_state.set_resolved(RETRY_HOOKS) |
2319 | - yield unit_state.clear_resolved() |
2320 | - yield unit_state.set_resolved(NO_HOOKS) |
2321 | - |
2322 | - yield self.poke_zk() |
2323 | - |
2324 | - self.assertEqual(len(results), 4) |
2325 | - self.assertIdentical(results.pop(0), False) |
2326 | - self.assertEqual(results.pop(0).type_name, "created") |
2327 | - self.assertEqual(results.pop(0).type_name, "deleted") |
2328 | - self.assertEqual(results.pop(0).type_name, "created") |
2329 | - |
2330 | - self.assertEqual( |
2331 | - (yield unit_state.get_resolved()), |
2332 | - {"retry": NO_HOOKS}) |
2333 | - |
2334 | - @inlineCallbacks |
2335 | - def test_watch_resolved_processes_current_state(self): |
2336 | - """The watch method processes the current state before returning.""" |
2337 | - unit_state = yield self.get_unit_state() |
2338 | - |
2339 | - results = [] |
2340 | - |
2341 | - @inlineCallbacks |
2342 | - def callback(value): |
2343 | - results.append((yield unit_state.get_resolved())) |
2344 | - |
2345 | - yield unit_state.watch_resolved(callback) |
2346 | - self.assertTrue(results) |
2347 | - |
2348 | - @inlineCallbacks |
2349 | - def test_stop_watch_resolved(self): |
2350 | - """A unit resolved watch can be instituted on a permanent basis. |
2351 | - |
2352 | - However the callback can raise StopWatcher at anytime to stop the watch |
2353 | - """ |
2354 | - unit_state = yield self.get_unit_state() |
2355 | - |
2356 | - results = [] |
2357 | - |
2358 | - def callback(value): |
2359 | - results.append(value) |
2360 | - if len(results) == 1: |
2361 | - raise StopWatcher() |
2362 | - if len(results) == 3: |
2363 | - raise StopWatcher() |
2364 | - |
2365 | - unit_state.watch_resolved(callback) |
2366 | - yield unit_state.set_resolved(RETRY_HOOKS) |
2367 | - yield unit_state.clear_resolved() |
2368 | - yield self.poke_zk() |
2369 | - |
2370 | - unit_state.watch_resolved(callback) |
2371 | - yield unit_state.set_resolved(NO_HOOKS) |
2372 | - yield unit_state.clear_resolved() |
2373 | - |
2374 | - yield self.poke_zk() |
2375 | - |
2376 | - self.assertEqual(len(results), 3) |
2377 | - self.assertIdentical(results.pop(0), False) |
2378 | - self.assertIdentical(results.pop(0), False) |
2379 | - self.assertEqual(results.pop(0).type_name, "created") |
2380 | - |
2381 | - self.assertEqual( |
2382 | - (yield unit_state.get_resolved()), None) |
2383 | - |
2384 | - @inlineCallbacks |
2385 | - def test_get_set_clear_relation_resolved(self): |
2386 | - """The a unit's realtions can be set to resolved to mark a |
2387 | - future transition, with an optional retry flag.""" |
2388 | - |
2389 | - unit_state = yield self.get_unit_state() |
2390 | - |
2391 | - self.assertIdentical((yield unit_state.get_relation_resolved()), None) |
2392 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
2393 | - |
2394 | - # Trying to set a conflicting raises an error |
2395 | - yield self.assertFailure( |
2396 | - unit_state.set_relation_resolved({"0": NO_HOOKS}), |
2397 | - ServiceUnitRelationResolvedAlreadyEnabled) |
2398 | - |
2399 | - # Doing the same thing is fine |
2400 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}), |
2401 | - |
2402 | - # Its fine to put in new values |
2403 | - yield unit_state.set_relation_resolved({"21": RETRY_HOOKS}) |
2404 | - yield self.assertEqual( |
2405 | - (yield unit_state.get_relation_resolved()), |
2406 | - {"0": RETRY_HOOKS, "21": RETRY_HOOKS}) |
2407 | - |
2408 | - yield unit_state.clear_relation_resolved() |
2409 | - self.assertIdentical((yield unit_state.get_relation_resolved()), None) |
2410 | - yield unit_state.clear_relation_resolved() |
2411 | - |
2412 | - yield self.assertFailure( |
2413 | - unit_state.set_relation_resolved(True), ValueError) |
2414 | - yield self.assertFailure( |
2415 | - unit_state.set_relation_resolved(None), ValueError) |
2416 | - |
2417 | - @inlineCallbacks |
2418 | - def test_watch_relation_resolved(self): |
2419 | - """A unit resolved watch can be instituted on a permanent basis.""" |
2420 | - unit_state = yield self.get_unit_state() |
2421 | - |
2422 | - results = [] |
2423 | - |
2424 | - def callback(value): |
2425 | - results.append(value) |
2426 | - |
2427 | - unit_state.watch_relation_resolved(callback) |
2428 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
2429 | - yield unit_state.clear_relation_resolved() |
2430 | - yield unit_state.set_relation_resolved({"0": NO_HOOKS}) |
2431 | - |
2432 | - yield self.poke_zk() |
2433 | - |
2434 | - self.assertEqual(len(results), 4) |
2435 | - self.assertIdentical(results.pop(0), False) |
2436 | - self.assertEqual(results.pop(0).type_name, "created") |
2437 | - self.assertEqual(results.pop(0).type_name, "deleted") |
2438 | - self.assertEqual(results.pop(0).type_name, "created") |
2439 | - |
2440 | - self.assertEqual( |
2441 | - (yield unit_state.get_relation_resolved()), |
2442 | - {"0": NO_HOOKS}) |
2443 | - |
2444 | - @inlineCallbacks |
2445 | - def test_watch_relation_resolved_processes_current_state(self): |
2446 | - """The watch method returns only after processing the current state.""" |
2447 | - unit_state = yield self.get_unit_state() |
2448 | - |
2449 | - results = [] |
2450 | - |
2451 | - @inlineCallbacks |
2452 | - def callback(value): |
2453 | - results.append((yield unit_state.get_relation_resolved())) |
2454 | - yield unit_state.watch_relation_resolved(callback) |
2455 | - self.assertTrue(results) |
2456 | - |
2457 | - @inlineCallbacks |
2458 | - def test_stop_watch_relation_resolved(self): |
2459 | - """A unit resolved watch can be instituted on a permanent basis.""" |
2460 | - unit_state = yield self.get_unit_state() |
2461 | - |
2462 | - results = [] |
2463 | - |
2464 | - def callback(value): |
2465 | - results.append(value) |
2466 | - |
2467 | - if len(results) == 1: |
2468 | - raise StopWatcher() |
2469 | - |
2470 | - if len(results) == 3: |
2471 | - raise StopWatcher() |
2472 | - |
2473 | - unit_state.watch_relation_resolved(callback) |
2474 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
2475 | - yield unit_state.clear_relation_resolved() |
2476 | - yield self.poke_zk() |
2477 | - self.assertEqual(len(results), 1) |
2478 | - |
2479 | - unit_state.watch_relation_resolved(callback) |
2480 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
2481 | - yield unit_state.clear_relation_resolved() |
2482 | - yield self.poke_zk() |
2483 | - self.assertEqual(len(results), 3) |
2484 | - self.assertIdentical(results.pop(0), False) |
2485 | - self.assertIdentical(results.pop(0), False) |
2486 | - self.assertEqual(results.pop(0).type_name, "created") |
2487 | - |
2488 | - self.assertEqual( |
2489 | - (yield unit_state.get_relation_resolved()), None) |
2490 | - |
2491 | - @inlineCallbacks |
2492 | - def test_watch_resolved_slow_callback(self): |
2493 | - """A slow watch callback is still invoked serially.""" |
2494 | - unit_state = yield self.get_unit_state() |
2495 | - |
2496 | - callbacks = [Deferred() for i in range(5)] |
2497 | - results = [] |
2498 | - contents = [] |
2499 | - |
2500 | - @inlineCallbacks |
2501 | - def watch(value): |
2502 | - results.append(value) |
2503 | - yield callbacks[len(results) - 1] |
2504 | - contents.append((yield unit_state.get_resolved())) |
2505 | - |
2506 | - callbacks[0].callback(True) |
2507 | - yield unit_state.watch_resolved(watch) |
2508 | - |
2509 | - # These get collapsed into a single event |
2510 | - yield unit_state.set_resolved(RETRY_HOOKS) |
2511 | - yield unit_state.clear_resolved() |
2512 | - yield self.poke_zk() |
2513 | - |
2514 | - # Verify the callback hasn't completed |
2515 | - self.assertEqual(len(results), 2) |
2516 | - self.assertEqual(len(contents), 1) |
2517 | - |
2518 | - # Let it finish |
2519 | - callbacks[1].callback(True) |
2520 | - yield self.poke_zk() |
2521 | - |
2522 | - # Verify result counts |
2523 | - self.assertEqual(len(results), 3) |
2524 | - self.assertEqual(len(contents), 2) |
2525 | - |
2526 | - # Verify result values. Even though we have created event, the |
2527 | - # setting retrieved shows the hook is not enabled. |
2528 | - self.assertEqual(results[-1].type_name, "deleted") |
2529 | - self.assertEqual(contents[-1], None) |
2530 | - |
2531 | - yield unit_state.set_resolved(NO_HOOKS) |
2532 | - callbacks[2].callback(True) |
2533 | - yield self.poke_zk() |
2534 | - |
2535 | - self.assertEqual(len(results), 4) |
2536 | - self.assertEqual(contents[-1], {"retry": NO_HOOKS}) |
2537 | - |
2538 | - # Clear out any pending activity. |
2539 | - yield self.poke_zk() |
2540 | - |
2541 | - @inlineCallbacks |
2542 | - def test_watch_relation_resolved_slow_callback(self): |
2543 | - """A slow watch callback is still invoked serially.""" |
2544 | - unit_state = yield self.get_unit_state() |
2545 | - |
2546 | - callbacks = [Deferred() for i in range(5)] |
2547 | - results = [] |
2548 | - contents = [] |
2549 | - |
2550 | - @inlineCallbacks |
2551 | - def watch(value): |
2552 | - results.append(value) |
2553 | - yield callbacks[len(results) - 1] |
2554 | - contents.append((yield unit_state.get_relation_resolved())) |
2555 | - |
2556 | - callbacks[0].callback(True) |
2557 | - yield unit_state.watch_relation_resolved(watch) |
2558 | - |
2559 | - # These get collapsed into a single event |
2560 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
2561 | - yield unit_state.clear_relation_resolved() |
2562 | - yield self.poke_zk() |
2563 | - |
2564 | - # Verify the callback hasn't completed |
2565 | - self.assertEqual(len(results), 2) |
2566 | - self.assertEqual(len(contents), 1) |
2567 | - |
2568 | - # Let it finish |
2569 | - callbacks[1].callback(True) |
2570 | - yield self.poke_zk() |
2571 | - |
2572 | - # Verify result counts |
2573 | - self.assertEqual(len(results), 3) |
2574 | - self.assertEqual(len(contents), 2) |
2575 | - |
2576 | - # Verify result values. Even though we have created event, the |
2577 | - # setting retrieved shows the hook is not enabled. |
2578 | - self.assertEqual(results[-1].type_name, "deleted") |
2579 | - self.assertEqual(contents[-1], None) |
2580 | - |
2581 | - yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
2582 | - callbacks[2].callback(True) |
2583 | - yield self.poke_zk() |
2584 | - |
2585 | - self.assertEqual(len(results), 4) |
2586 | - self.assertEqual(contents[-1], {"0": RETRY_HOOKS}) |
2587 | - |
2588 | - # Clear out any pending activity. |
2589 | - yield self.poke_zk() |
2590 | - |
2591 | - @inlineCallbacks |
2592 | - def test_set_and_clear_upgrade_flag(self): |
2593 | - """An upgrade flag can be set on a unit.""" |
2594 | - |
2595 | - # Defaults to false |
2596 | - unit_state = yield self.get_unit_state() |
2597 | - upgrade_flag = yield unit_state.get_upgrade_flag() |
2598 | - self.assertEqual(upgrade_flag, False) |
2599 | - |
2600 | - # Can be set |
2601 | - yield unit_state.set_upgrade_flag() |
2602 | - upgrade_flag = yield unit_state.get_upgrade_flag() |
2603 | - self.assertEqual(upgrade_flag, {'force': False}) |
2604 | - |
2605 | - # Attempting to set multiple times is an error if the values |
2606 | - # differ. |
2607 | - yield self.assertFailure( |
2608 | - unit_state.set_upgrade_flag(force=True), |
2609 | - ServiceUnitUpgradeAlreadyEnabled) |
2610 | - self.assertEqual(upgrade_flag, {'force': False}) |
2611 | - |
2612 | - # Can be cleared |
2613 | - yield unit_state.clear_upgrade_flag() |
2614 | - upgrade_flag = yield unit_state.get_upgrade_flag() |
2615 | - self.assertEqual(upgrade_flag, False) |
2616 | - |
2617 | - # Can be cleared multiple times |
2618 | - yield unit_state.clear_upgrade_flag() |
2619 | - upgrade_flag = yield unit_state.get_upgrade_flag() |
2620 | - self.assertEqual(upgrade_flag, False) |
2621 | - |
2622 | - # A empty node present is not problematic |
2623 | - yield self.client.create(unit_state._upgrade_flag_path, "") |
2624 | - upgrade_flag = yield unit_state.get_upgrade_flag() |
2625 | - self.assertEqual(upgrade_flag, False) |
2626 | - |
2627 | - yield unit_state.set_upgrade_flag(force=True) |
2628 | - upgrade_flag = yield unit_state.get_upgrade_flag() |
2629 | - self.assertEqual(upgrade_flag, {"force": True}) |
2630 | - |
2631 | - @inlineCallbacks |
2632 | - def test_watch_upgrade_flag_once(self): |
2633 | - """An upgrade watch can be set to notified of presence and changes.""" |
2634 | - unit_state = yield self.get_unit_state() |
2635 | - yield unit_state.set_upgrade_flag() |
2636 | - |
2637 | - results = [] |
2638 | - |
2639 | - def callback(value): |
2640 | - results.append(value) |
2641 | - |
2642 | - unit_state.watch_upgrade_flag(callback, permanent=False) |
2643 | - yield unit_state.clear_upgrade_flag() |
2644 | - yield unit_state.set_upgrade_flag(force=True) |
2645 | - yield self.sleep(0.1) |
2646 | - yield self.poke_zk() |
2647 | - self.assertEqual(len(results), 2) |
2648 | - self.assertIdentical(results.pop(0), True) |
2649 | - self.assertIdentical(results.pop().type_name, "deleted") |
2650 | - |
2651 | - self.assertEqual( |
2652 | - (yield unit_state.get_upgrade_flag()), |
2653 | - {'force': True}) |
2654 | - |
2655 | - @inlineCallbacks |
2656 | - def test_watch_upgrade_processes_current_state(self): |
2657 | - unit_state = yield self.get_unit_state() |
2658 | - results = [] |
2659 | - |
2660 | - @inlineCallbacks |
2661 | - def callback(value): |
2662 | - results.append((yield unit_state.get_upgrade_flag())) |
2663 | - |
2664 | - yield unit_state.watch_upgrade_flag(callback) |
2665 | - self.assertTrue(results) |
2666 | - |
2667 | - @inlineCallbacks |
2668 | - def test_watch_upgrade_flag_permanent(self): |
2669 | - """An upgrade watch can be instituted on a permanent basis.""" |
2670 | - unit_state = yield self.get_unit_state() |
2671 | - |
2672 | - results = [] |
2673 | - |
2674 | - def callback(value): |
2675 | - results.append(value) |
2676 | - |
2677 | - yield unit_state.watch_upgrade_flag(callback) |
2678 | - self.assertTrue(results) |
2679 | - yield unit_state.set_upgrade_flag() |
2680 | - yield unit_state.clear_upgrade_flag() |
2681 | - yield unit_state.set_upgrade_flag() |
2682 | - |
2683 | - yield self.poke_zk() |
2684 | - |
2685 | - self.assertEqual(len(results), 4) |
2686 | - self.assertIdentical(results.pop(0), False) |
2687 | - self.assertIdentical(results.pop(0).type_name, "created") |
2688 | - self.assertIdentical(results.pop(0).type_name, "deleted") |
2689 | - self.assertIdentical(results.pop(0).type_name, "created") |
2690 | - |
2691 | - self.assertEqual( |
2692 | - (yield unit_state.get_upgrade_flag()), |
2693 | - {'force': False}) |
2694 | - |
2695 | - @inlineCallbacks |
2696 | - def test_watch_upgrade_flag_waits_on_slow_callbacks(self): |
2697 | - """A slow watch callback is still invoked serially.""" |
2698 | - unit_state = yield self.get_unit_state() |
2699 | - |
2700 | - callbacks = [Deferred() for i in range(5)] |
2701 | - results = [] |
2702 | - contents = [] |
2703 | - |
2704 | - @inlineCallbacks |
2705 | - def watch(value): |
2706 | - results.append(value) |
2707 | - yield callbacks[len(results) - 1] |
2708 | - contents.append((yield unit_state.get_upgrade_flag())) |
2709 | - |
2710 | - yield callbacks[0].callback(True) |
2711 | - yield unit_state.watch_upgrade_flag(watch) |
2712 | - |
2713 | - # These get collapsed into a single event |
2714 | - yield unit_state.set_upgrade_flag() |
2715 | - yield unit_state.clear_upgrade_flag() |
2716 | - yield self.poke_zk() |
2717 | - |
2718 | - # Verify the callback hasn't completed |
2719 | - self.assertEqual(len(results), 2) |
2720 | - self.assertEqual(len(contents), 1) |
2721 | - |
2722 | - # Let it finish |
2723 | - callbacks[1].callback(True) |
2724 | - yield self.poke_zk() |
2725 | - |
2726 | - # Verify result counts |
2727 | - self.assertEqual(len(results), 3) |
2728 | - self.assertEqual(len(contents), 2) |
2729 | - |
2730 | - # Verify result values. Even though we have created event, the |
2731 | - # setting retrieved shows the hook is not enabled. |
2732 | - self.assertEqual(results[-1].type_name, "deleted") |
2733 | - self.assertEqual(contents[-1], False) |
2734 | - |
2735 | - yield unit_state.set_upgrade_flag() |
2736 | - yield self.poke_zk() |
2737 | - |
2738 | - # Verify the callback hasn't completed |
2739 | - self.assertEqual(len(contents), 2) |
2740 | - |
2741 | - callbacks[2].callback(True) |
2742 | - yield self.poke_zk() |
2743 | - |
2744 | - # Verify values. |
2745 | - self.assertEqual(len(contents), 3) |
2746 | - self.assertEqual(results[-1].type_name, "created") |
2747 | - self.assertEqual(contents[-1], {'force': False}) |
2748 | - |
2749 | - # Clear out any pending activity. |
2750 | - yield self.poke_zk() |
2751 | - |
2752 | - @inlineCallbacks |
2753 | - def test_enable_debug_hook(self): |
2754 | - """Unit hook debugging can be enabled on the unit state.""" |
2755 | - unit_state = yield self.get_unit_state() |
2756 | - enabled = yield unit_state.enable_hook_debug(["*"]) |
2757 | - self.assertIdentical(enabled, True) |
2758 | - content, stat = yield self.client.get( |
2759 | - "/units/%s/debug" % unit_state.internal_id) |
2760 | - data = yaml.load(content) |
2761 | - self.assertEqual(data, {"debug_hooks": ["*"]}) |
2762 | - |
2763 | - @inlineCallbacks |
2764 | - def test_enable_debug_multiple_named_hooks(self): |
2765 | - """Unit hook debugging can be enabled for multiple hooks.""" |
2766 | - unit_state = yield self.get_unit_state() |
2767 | - enabled = yield unit_state.enable_hook_debug( |
2768 | - ["db-relation-broken", "db-relation-changed"]) |
2769 | - |
2770 | - self.assertIdentical(enabled, True) |
2771 | - content, stat = yield self.client.get( |
2772 | - "/units/%s/debug" % unit_state.internal_id) |
2773 | - data = yaml.load(content) |
2774 | - self.assertEqual( |
2775 | - data, |
2776 | - {"debug_hooks": ["db-relation-broken", "db-relation-changed"]}) |
2777 | - |
2778 | - @inlineCallbacks |
2779 | - def test_enable_debug_all_and_named_is_error(self): |
2780 | - """Unit hook debugging can be enabled for multiple hooks, |
2781 | - but only if they are all named hooks.""" |
2782 | - unit_state = yield self.get_unit_state() |
2783 | - |
2784 | - error = yield self.assertFailure( |
2785 | - unit_state.enable_hook_debug(["*", "db-relation-changed"]), |
2786 | - ValueError) |
2787 | - self.assertEquals( |
2788 | - str(error), |
2789 | - "Ambigious to debug all hooks and named hooks " |
2790 | - "['*', 'db-relation-changed']") |
2791 | - |
2792 | - @inlineCallbacks |
2793 | - def test_enable_debug_requires_sequence(self): |
2794 | - """The enable hook debug only accepts a sequences of names. |
2795 | - """ |
2796 | - unit_state = yield self.get_unit_state() |
2797 | - |
2798 | - error = yield self.assertFailure( |
2799 | - unit_state.enable_hook_debug(None), |
2800 | - AssertionError) |
2801 | - self.assertEquals(str(error), "Hook names must be a list: got None") |
2802 | - |
2803 | - @inlineCallbacks |
2804 | - def test_enable_named_debug_hook(self): |
2805 | - """Unit hook debugging can be enabled on for a named hook.""" |
2806 | - unit_state = yield self.get_unit_state() |
2807 | - enabled = yield unit_state.enable_hook_debug( |
2808 | - ["db-relation-changed"]) |
2809 | - self.assertIdentical(enabled, True) |
2810 | - content, stat = yield self.client.get( |
2811 | - "/units/%s/debug" % unit_state.internal_id) |
2812 | - data = yaml.load(content) |
2813 | - self.assertEqual(data, {"debug_hooks": ["db-relation-changed"]}) |
2814 | - |
2815 | - @inlineCallbacks |
2816 | - def test_enable_debug_hook_pre_existing(self): |
2817 | - """Attempting to enable debug on a unit state already being debugged |
2818 | - raises an exception. |
2819 | - """ |
2820 | - unit_state = yield self.get_unit_state() |
2821 | - yield unit_state.enable_hook_debug(["*"]) |
2822 | - error = yield self.assertFailure(unit_state.enable_hook_debug(["*"]), |
2823 | - ServiceUnitDebugAlreadyEnabled) |
2824 | - self.assertEquals( |
2825 | - str(error), "Service unit 'wordpress/0' is already in debug mode.") |
2826 | - |
2827 | - @inlineCallbacks |
2828 | - def test_enable_debug_hook_lifetime(self): |
2829 | - """A debug hook setting is only active for the lifetime of the client |
2830 | - that created it. |
2831 | - """ |
2832 | - unit_state = yield self.get_unit_state() |
2833 | - yield unit_state.enable_hook_debug(["*"]) |
2834 | - exists = yield self.client.exists( |
2835 | - "/units/%s/debug" % unit_state.internal_id) |
2836 | - self.assertTrue(exists) |
2837 | - yield self.client.close() |
2838 | - self.client = self.get_zookeeper_client() |
2839 | - yield self.client.connect() |
2840 | - exists = yield self.client.exists( |
2841 | - "/units/%s/debug" % unit_state.internal_id) |
2842 | - self.assertFalse(exists) |
2843 | - |
2844 | - @inlineCallbacks |
2845 | - def test_watch_debug_hook_once(self): |
2846 | - """A watch can be set to notified of presence and changes.""" |
2847 | - unit_state = yield self.get_unit_state() |
2848 | - yield unit_state.enable_hook_debug(["*"]) |
2849 | - |
2850 | - results = [] |
2851 | - |
2852 | - def callback(value): |
2853 | - results.append(value) |
2854 | - |
2855 | - yield unit_state.watch_hook_debug(callback, permanent=False) |
2856 | - yield unit_state.clear_hook_debug() |
2857 | - yield unit_state.enable_hook_debug(["*"]) |
2858 | - yield self.poke_zk() |
2859 | - self.assertEqual(len(results), 2) |
2860 | - self.assertIdentical(results.pop(0), True) |
2861 | - self.assertIdentical(results.pop().type_name, "deleted") |
2862 | - |
2863 | - self.assertEqual( |
2864 | - (yield unit_state.get_hook_debug()), |
2865 | - {"debug_hooks": ["*"]}) |
2866 | - |
2867 | - @inlineCallbacks |
2868 | - def test_watch_debug_hook_processes_current_state(self): |
2869 | - """A hook debug watch can be instituted on a permanent basis.""" |
2870 | - unit_state = yield self.get_unit_state() |
2871 | - |
2872 | - results = [] |
2873 | - |
2874 | - @inlineCallbacks |
2875 | - def callback(value): |
2876 | - results.append((yield unit_state.get_hook_debug())) |
2877 | - |
2878 | - yield unit_state.watch_hook_debug(callback) |
2879 | - self.assertTrue(results) |
2880 | - |
2881 | - @inlineCallbacks |
2882 | - def test_watch_debug_hook_permanent(self): |
2883 | - """A hook debug watch can be instituted on a permanent basis.""" |
2884 | - unit_state = yield self.get_unit_state() |
2885 | - |
2886 | - results = [] |
2887 | - |
2888 | - def callback(value): |
2889 | - results.append(value) |
2890 | - |
2891 | - yield unit_state.watch_hook_debug(callback) |
2892 | - yield unit_state.enable_hook_debug(["*"]) |
2893 | - yield unit_state.clear_hook_debug() |
2894 | - yield unit_state.enable_hook_debug(["*"]) |
2895 | - |
2896 | - yield self.poke_zk() |
2897 | - |
2898 | - self.assertEqual(len(results), 4) |
2899 | - self.assertIdentical(results.pop(0), False) |
2900 | - self.assertIdentical(results.pop(0).type_name, "created") |
2901 | - self.assertIdentical(results.pop(0).type_name, "deleted") |
2902 | - self.assertIdentical(results.pop(0).type_name, "created") |
2903 | - |
2904 | - self.assertEqual( |
2905 | - (yield unit_state.get_hook_debug()), |
2906 | - {"debug_hooks": ["*"]}) |
2907 | - |
2908 | - @inlineCallbacks |
2909 | - def test_watch_debug_hook_waits_on_slow_callbacks(self): |
2910 | - """A slow watch callback is still invoked serially.""" |
2911 | - |
2912 | - unit_state = yield self.get_unit_state() |
2913 | - |
2914 | - callbacks = [Deferred() for i in range(5)] |
2915 | - results = [] |
2916 | - contents = [] |
2917 | - |
2918 | - @inlineCallbacks |
2919 | - def watch(value): |
2920 | - results.append(value) |
2921 | - yield callbacks[len(results) - 1] |
2922 | - contents.append((yield unit_state.get_hook_debug())) |
2923 | - |
2924 | - callbacks[0].callback(True) # Finish the current state processing |
2925 | - yield unit_state.watch_hook_debug(watch) |
2926 | - |
2927 | - # These get collapsed into a single event |
2928 | - yield unit_state.enable_hook_debug(["*"]) |
2929 | - yield unit_state.clear_hook_debug() |
2930 | - yield self.poke_zk() |
2931 | - |
2932 | - # Verify the callback hasn't completed |
2933 | - self.assertEqual(len(results), 2) |
2934 | - self.assertEqual(len(contents), 1) |
2935 | - |
2936 | - # Let it finish |
2937 | - callbacks[1].callback(True) |
2938 | - yield self.poke_zk() |
2939 | - |
2940 | - # Verify result counts |
2941 | - self.assertEqual(len(results), 3) |
2942 | - self.assertEqual(len(contents), 2) |
2943 | - |
2944 | - # Verify result values. Even though we have created event, the |
2945 | - # setting retrieved shows the hook is not enabled. |
2946 | - self.assertEqual(results[-1].type_name, "deleted") |
2947 | - self.assertEqual(contents[-1], None) |
2948 | - |
2949 | - yield unit_state.enable_hook_debug(["*"]) |
2950 | - yield self.poke_zk() |
2951 | - |
2952 | - # Verify the callback hasn't completed |
2953 | - self.assertEqual(len(contents), 2) |
2954 | - |
2955 | - callbacks[2].callback(True) |
2956 | - yield self.poke_zk() |
2957 | - |
2958 | - # Verify values. |
2959 | - self.assertEqual(len(contents), 3) |
2960 | - self.assertEqual(results[-1].type_name, "created") |
2961 | - self.assertEqual(contents[-1], {"debug_hooks": ["*"]}) |
2962 | - |
2963 | - # Clear out any pending activity. |
2964 | - yield self.poke_zk() |
2965 | - |
2966 | - @inlineCallbacks |
2967 | - def test_service_unit_agent(self): |
2968 | - """A service unit state has an associated unit agent.""" |
2969 | - service_state = yield self.service_state_manager.add_service_state( |
2970 | - "wordpress", self.charm_state, dummy_constraints) |
2971 | - unit_state = yield service_state.add_unit_state() |
2972 | - exists_d, watch_d = unit_state.watch_agent() |
2973 | - exists = yield exists_d |
2974 | - self.assertFalse(exists) |
2975 | - yield unit_state.connect_agent() |
2976 | - event = yield watch_d |
2977 | - self.assertEqual(event.type_name, "created") |
2978 | - self.assertEqual(event.path, |
2979 | - "/units/%s/agent" % unit_state.internal_id) |
2980 | - |
2981 | - @inlineCallbacks |
2982 | - def test_get_charm_id(self): |
2983 | - """A service unit knows its charm id""" |
2984 | - service_state = yield self.service_state_manager.add_service_state( |
2985 | - "wordpress", self.charm_state, dummy_constraints) |
2986 | - unit_state = yield service_state.add_unit_state() |
2987 | - unit_charm = yield unit_state.get_charm_id() |
2988 | - service_charm = yield service_state.get_charm_id() |
2989 | - self.assertTrue( |
2990 | - (self.charm_state.id == unit_charm == service_charm)) |
2991 | - |
2992 | - @inlineCallbacks |
2993 | - def test_set_charm_id(self): |
2994 | - """A service unit charm can be set and is validated when set.""" |
2995 | - service_state = yield self.service_state_manager.add_service_state( |
2996 | - "wordpress", self.charm_state, dummy_constraints) |
2997 | - unit_state = yield service_state.add_unit_state() |
2998 | - yield self.assertFailure( |
2999 | - unit_state.set_charm_id("abc"), CharmURLError) |
3000 | - yield self.assertFailure( |
3001 | - unit_state.set_charm_id("abc:foobar-a"), CharmURLError) |
3002 | - yield self.assertFailure( |
3003 | - unit_state.set_charm_id(None), CharmURLError) |
3004 | - charm_id = "local:series/name-1" |
3005 | - yield unit_state.set_charm_id(charm_id) |
3006 | - value = yield unit_state.get_charm_id() |
3007 | - self.assertEqual(charm_id, value) |
3008 | - |
3009 | - @inlineCallbacks |
3010 | - def test_add_unit_state_combines_constraints(self): |
3011 | - """Constraints are inherited both from juju defaults and service""" |
3012 | - service_state = yield self.service_state_manager.add_service_state( |
3013 | - "wordpress", self.charm_state, |
3014 | - dummy_cs.parse(["arch=arm", "mem=1G"])) |
3015 | - unit_state = yield service_state.add_unit_state() |
3016 | - constraints = yield unit_state.get_constraints() |
3017 | - expected = { |
3018 | - "arch": "arm", "cpu": 1, "mem": 1024, |
3019 | - "provider-type": "dummy", "ubuntu-series": "series"} |
3020 | - self.assertEquals(constraints, expected) |
3021 | - |
3022 | - @inlineCallbacks |
3023 | - def test_unassign_unit_from_machine_without_being_assigned(self): |
3024 | - """ |
3025 | - When unassigning a machine from a unit, it is possible that |
3026 | - the machine has not been previously assigned, or that it |
3027 | - was assigned but the state changed beneath us. In either |
3028 | - case, the end state is the intended state, so we simply |
3029 | - move forward without any errors here, to avoid having to |
3030 | - handle the extra complexity of dealing with the concurrency |
3031 | - problems. |
3032 | - """ |
3033 | - service_state = yield self.service_state_manager.add_service_state( |
3034 | - "wordpress", self.charm_state, dummy_constraints) |
3035 | - unit_state = yield service_state.add_unit_state() |
3036 | - |
3037 | - yield unit_state.unassign_from_machine() |
3038 | - |
3039 | - topology = yield self.get_topology() |
3040 | - self.assertEquals( |
3041 | - topology.get_service_unit_machine(service_state.internal_id, |
3042 | - unit_state.internal_id), |
3043 | - None) |
3044 | - |
3045 | - machine_id = yield unit_state.get_assigned_machine_id() |
3046 | - self.assertEqual(machine_id, None) |
3047 | - |
3048 | - @inlineCallbacks |
3049 | - def test_assign_unit_to_machine_again_fails(self): |
3050 | - """ |
3051 | - Trying to assign a machine to an already assigned unit |
3052 | - should fail, unless we're assigning to precisely the same |
3053 | - machine, in which case it's no big deal. |
3054 | - """ |
3055 | - service_state = yield self.service_state_manager.add_service_state( |
3056 | - "wordpress", self.charm_state, dummy_constraints) |
3057 | - unit_state = yield service_state.add_unit_state() |
3058 | - machine_state0 = yield self.machine_state_manager.add_machine_state( |
3059 | - series_constraints) |
3060 | - machine_state1 = yield self.machine_state_manager.add_machine_state( |
3061 | - series_constraints) |
3062 | - |
3063 | - yield unit_state.assign_to_machine(machine_state0) |
3064 | - |
3065 | - # Assigning again to the same machine is a NOOP, so nothing |
3066 | - # terrible should happen if we let it go through. |
3067 | - yield unit_state.assign_to_machine(machine_state0) |
3068 | - |
3069 | - try: |
3070 | - yield unit_state.assign_to_machine(machine_state1) |
3071 | - except ServiceUnitStateMachineAlreadyAssigned, e: |
3072 | - self.assertEquals(e.unit_name, "wordpress/0") |
3073 | - else: |
3074 | - self.fail("Error not raised") |
3075 | - |
3076 | - machine_id = yield unit_state.get_assigned_machine_id() |
3077 | - self.assertEqual(machine_id, 0) |
3078 | - |
3079 | - @inlineCallbacks |
3080 | - def test_unassign_unit_from_machine_with_changing_state(self): |
3081 | - service_state = yield self.service_state_manager.add_service_state( |
3082 | - "wordpress", self.charm_state, dummy_constraints) |
3083 | - unit_state = yield service_state.add_unit_state() |
3084 | - |
3085 | - yield self.remove_service_unit(service_state.internal_id, |
3086 | - unit_state.internal_id) |
3087 | - |
3088 | - d = unit_state.unassign_from_machine() |
3089 | - yield self.assertFailure(d, StateChanged) |
3090 | - |
3091 | - d = unit_state.get_assigned_machine_id() |
3092 | - yield self.assertFailure(d, StateChanged) |
3093 | - |
3094 | - yield self.remove_service(service_state.internal_id) |
3095 | - |
3096 | - d = unit_state.unassign_from_machine() |
3097 | - yield self.assertFailure(d, StateChanged) |
3098 | - |
3099 | - d = unit_state.get_assigned_machine_id() |
3100 | - yield self.assertFailure(d, StateChanged) |
3101 | - |
3102 | - @inlineCallbacks |
3103 | - def test_assign_unit_to_unused_machine(self): |
3104 | - """Verify that unused machines can be assigned to when their machine |
3105 | - constraints match the service unit's.""" |
3106 | - yield self.machine_state_manager.add_machine_state( |
3107 | - series_constraints) |
3108 | - mysql_service_state = yield self.add_service_from_charm("mysql") |
3109 | - mysql_unit_state = yield mysql_service_state.add_unit_state() |
3110 | - mysql_machine_state = yield \ |
3111 | - self.machine_state_manager.add_machine_state(series_constraints) |
3112 | - yield mysql_unit_state.assign_to_machine(mysql_machine_state) |
3113 | - yield self.service_state_manager.remove_service_state( |
3114 | - mysql_service_state) |
3115 | - wordpress_service_state = yield self.add_service_from_charm( |
3116 | - "wordpress") |
3117 | - wordpress_unit_state = yield wordpress_service_state.add_unit_state() |
3118 | - yield wordpress_unit_state.assign_to_unused_machine() |
3119 | - self.assertEqual( |
3120 | - (yield self.get_topology()).get_machines(), |
3121 | - ["machine-0000000000", "machine-0000000001"]) |
3122 | - yield self.assert_machine_assignments("wordpress", [1]) |
3123 | - |
3124 | - @inlineCallbacks |
3125 | - def test_assign_unit_to_unused_machine_bad_constraints(self): |
3126 | - """Verify that unused machines do not get allocated service units with |
3127 | - non-matching constraints.""" |
3128 | - yield self.machine_state_manager.add_machine_state( |
3129 | - series_constraints) |
3130 | - mysql_service_state = yield self.add_service_from_charm("mysql") |
3131 | - mysql_unit_state = yield mysql_service_state.add_unit_state() |
3132 | - mysql_machine_state = yield \ |
3133 | - self.machine_state_manager.add_machine_state( |
3134 | - series_constraints) |
3135 | - yield mysql_unit_state.assign_to_machine(mysql_machine_state) |
3136 | - yield self.service_state_manager.remove_service_state( |
3137 | - mysql_service_state) |
3138 | - other_constraints = dummy_cs.parse(["arch=arm"]) |
3139 | - wordpress_service_state = yield self.add_service_from_charm( |
3140 | - "wordpress", constraints=other_constraints.with_series("series")) |
3141 | - wordpress_unit_state = yield wordpress_service_state.add_unit_state() |
3142 | - yield self.assertFailure( |
3143 | - wordpress_unit_state.assign_to_unused_machine(), |
3144 | - NoUnusedMachines) |
3145 | - self.assertEqual( |
3146 | - (yield self.get_topology()).get_machines(), |
3147 | - ["machine-0000000000", "machine-0000000001"]) |
3148 | - yield self.assert_machine_assignments("wordpress", [None]) |
3149 | - |
3150 | - @inlineCallbacks |
3151 | - def test_assign_unit_to_unused_machine_with_changing_state_service(self): |
3152 | - """Verify `StateChanged` raised if service is manipulated during reuse. |
3153 | - """ |
3154 | - yield self.machine_state_manager.add_machine_state( |
3155 | - series_constraints) |
3156 | - mysql_service_state = yield self.add_service_from_charm("mysql") |
3157 | - mysql_unit_state = yield mysql_service_state.add_unit_state() |
3158 | - mysql_machine_state = yield self.machine_state_manager.add_machine_state( |
3159 | - series_constraints) |
3160 | - yield mysql_unit_state.assign_to_machine(mysql_machine_state) |
3161 | - yield self.service_state_manager.remove_service_state( |
3162 | - mysql_service_state) |
3163 | - wordpress_service_state = yield self.add_service_from_charm( |
3164 | - "wordpress") |
3165 | - wordpress_unit_state = yield wordpress_service_state.add_unit_state() |
3166 | - yield self.remove_service(wordpress_service_state.internal_id) |
3167 | - yield self.assertFailure( |
3168 | - wordpress_unit_state.assign_to_unused_machine(), StateChanged) |
3169 | - |
3170 | - @inlineCallbacks |
3171 | - def test_assign_unit_to_unused_machine_with_changing_state_service_unit(self): |
3172 | - "Verify `StateChanged` raised if unit is manipulated during reuse.""" |
3173 | - yield self.machine_state_manager.add_machine_state( |
3174 | - series_constraints) |
3175 | - mysql_service_state = yield self.add_service_from_charm("mysql") |
3176 | - mysql_unit_state = yield mysql_service_state.add_unit_state() |
3177 | - mysql_machine_state = yield self.machine_state_manager.add_machine_state( |
3178 | - series_constraints) |
3179 | - yield mysql_unit_state.assign_to_machine(mysql_machine_state) |
3180 | - yield self.service_state_manager.remove_service_state( |
3181 | - mysql_service_state) |
3182 | - wordpress_service_state = yield self.add_service_from_charm( |
3183 | - "wordpress") |
3184 | - wordpress_unit_state = yield wordpress_service_state.add_unit_state() |
3185 | - yield self.remove_service_unit( |
3186 | - wordpress_service_state.internal_id, |
3187 | - wordpress_unit_state.internal_id) |
3188 | - yield self.assertFailure( |
3189 | - wordpress_unit_state.assign_to_unused_machine(), StateChanged) |
3190 | - |
3191 | - @inlineCallbacks |
3192 | - def test_assign_unit_to_unused_machine_only_machine_zero(self): |
3193 | - """Verify when the only available machine is machine 0""" |
3194 | - yield self.machine_state_manager.add_machine_state( |
3195 | - series_constraints) |
3196 | - wordpress_service_state = yield self.add_service_from_charm( |
3197 | - "wordpress") |
3198 | - wordpress_unit_state = yield wordpress_service_state.add_unit_state() |
3199 | - yield self.assertFailure( |
3200 | - wordpress_unit_state.assign_to_unused_machine(), |
3201 | - NoUnusedMachines) |
3202 | - |
3203 | - @inlineCallbacks |
3204 | - def test_assign_unit_to_unused_machine_none_available(self): |
3205 | - """Verify when there are no unused machines""" |
3206 | - yield self.machine_state_manager.add_machine_state( |
3207 | - series_constraints) |
3208 | - mysql_service_state = yield self.add_service_from_charm("mysql") |
3209 | - mysql_unit_state = yield mysql_service_state.add_unit_state() |
3210 | - mysql_machine_state = yield self.machine_state_manager.add_machine_state( |
3211 | - series_constraints) |
3212 | - yield mysql_unit_state.assign_to_machine(mysql_machine_state) |
3213 | - yield self.assert_machine_assignments("mysql", [1]) |
3214 | - wordpress_service_state = yield self.add_service_from_charm( |
3215 | - "wordpress") |
3216 | - wordpress_unit_state = yield wordpress_service_state.add_unit_state() |
3217 | - yield self.assertFailure( |
3218 | - wordpress_unit_state.assign_to_unused_machine(), |
3219 | - NoUnusedMachines) |
3220 | - |
3221 | - @inlineCallbacks |
3222 | - def test_watch_relations_processes_current_state(self): |
3223 | - """ |
3224 | - The watch method returns only after processing initial state. |
3225 | - |
3226 | - Note the callback is only invoked if there are changes |
3227 | - requiring processing. |
3228 | - """ |
3229 | - service_state = yield self.add_service("wordpress") |
3230 | - yield self.add_relation( |
3231 | - "rel-type", "global", [service_state, "name", "role"]) |
3232 | - |
3233 | - results = [] |
3234 | - |
3235 | - def callback(*args): |
3236 | - results.append(True) |
3237 | - |
3238 | - yield service_state.watch_relation_states(callback) |
3239 | - self.assertTrue(results) |
3240 | - |
3241 | - @inlineCallbacks |
3242 | - def test_watch_relations_when_being_created(self): |
3243 | - """ |
3244 | - We can watch relations before we have any. |
3245 | - """ |
3246 | - service_state = yield self.add_service("wordpress") |
3247 | - |
3248 | - wait_callback = [Deferred() for i in range(5)] |
3249 | - calls = [] |
3250 | - |
3251 | - def watch_relations(old_relations, new_relations): |
3252 | - calls.append((old_relations, new_relations)) |
3253 | - wait_callback[len(calls) - 1].callback(True) |
3254 | - |
3255 | - # Start watching |
3256 | - service_state.watch_relation_states(watch_relations) |
3257 | - |
3258 | - # Callback is still untouched |
3259 | - self.assertEquals(calls, []) |
3260 | - |
3261 | - # add a service relation and wait for the callback |
3262 | - relation_state = yield self.add_relation( |
3263 | - "rel-type", "global", [service_state, "name", "role"]) |
3264 | - yield wait_callback[1] |
3265 | - |
3266 | - # verify the result |
3267 | - self.assertEquals(len(calls), 2) |
3268 | - old_relations, new_relations = calls[1] |
3269 | - self.assertFalse(old_relations) |
3270 | - self.assertEquals(new_relations[0].relation_role, "role") |
3271 | - |
3272 | - # add a new relation with the service assigned to it. |
3273 | - relation_state2 = yield self.add_relation( |
3274 | - "rel-type2", "global", [service_state, "app", "server"]) |
3275 | - yield wait_callback[2] |
3276 | - |
3277 | - self.assertEquals(len(calls), 3) |
3278 | - old_relations, new_relations = calls[2] |
3279 | - self.assertEquals([r.internal_relation_id for r in old_relations], |
3280 | - [relation_state.internal_id]) |
3281 | - self.assertEquals([r.internal_relation_id for r in new_relations], |
3282 | - [relation_state.internal_id, |
3283 | - relation_state2.internal_id]) |
3284 | - |
3285 | - @inlineCallbacks |
3286 | - def test_watch_relations_may_defer(self): |
3287 | - """ |
3288 | - The watch relations callback may return a deferred so that |
3289 | - it performs some its logic asynchronously. In this case, it must |
3290 | - not be called a second time before its postponed logic is finished |
3291 | - completely. |
3292 | - """ |
3293 | - wait_callback = [Deferred() for i in range(5)] |
3294 | - finish_callback = [Deferred() for i in range(5)] |
3295 | - |
3296 | - calls = [] |
3297 | - |
3298 | - def watch_relations(old_relations, new_relations): |
3299 | - calls.append((old_relations, new_relations)) |
3300 | - wait_callback[len(calls) - 1].callback(True) |
3301 | - return finish_callback[len(calls) - 1] |
3302 | - |
3303 | - service_state = yield self.add_service("s-1") |
3304 | - service_state.watch_relation_states(watch_relations) |
3305 | - |
3306 | - # Shouldn't have any callbacks yet. |
3307 | - self.assertEquals(calls, []) |
3308 | - |
3309 | - # Assign to a relation. |
3310 | - yield self.add_relation("rel-type", "global", |
3311 | - (service_state, "name", "role")) |
3312 | - |
3313 | - # Hold off until callback is started. |
3314 | - yield wait_callback[0] |
3315 | - |
3316 | - # Assign to another relation. |
3317 | - yield self.add_relation("rel-type", "global", |
3318 | - (service_state, "name2", "role")) |
3319 | - |
3320 | - # Give a chance for something bad to happen. |
3321 | - yield self.sleep(0.3) |
3322 | - |
3323 | - # Ensure we still have a single call. |
3324 | - self.assertEquals(len(calls), 1) |
3325 | - |
3326 | - # Allow the first call to be completed, and wait on the |
3327 | - # next one. |
3328 | - finish_callback[0].callback(None) |
3329 | - yield wait_callback[1] |
3330 | - finish_callback[1].callback(None) |
3331 | - |
3332 | - # We should have the second change now. |
3333 | - self.assertEquals(len(calls), 2) |
3334 | - |
3335 | - @inlineCallbacks |
3336 | - def test_get_relation_endpoints_service_name(self): |
3337 | - """Test getting endpoints with descriptor ``<service name>``""" |
3338 | - yield self.add_service_from_charm("wordpress") |
3339 | - self.assertEqual( |
3340 | - (yield self.service_state_manager.get_relation_endpoints( |
3341 | - "wordpress")), |
3342 | - [RelationEndpoint("wordpress", "varnish", "cache", "client"), |
3343 | - RelationEndpoint("wordpress", "mysql", "db", "client"), |
3344 | - RelationEndpoint("wordpress", "http", "url", "server"), |
3345 | - RelationEndpoint("wordpress", "juju-info", "juju-info", "server")]) |
3346 | - yield self.add_service_from_charm("riak") |
3347 | - self.assertEqual( |
3348 | - (yield self.service_state_manager.get_relation_endpoints( |
3349 | - "riak")), |
3350 | - [RelationEndpoint("riak", "http", "admin", "server"), |
3351 | - RelationEndpoint("riak", "http", "endpoint", "server"), |
3352 | - RelationEndpoint("riak", "riak", "ring", "peer"), |
3353 | - RelationEndpoint("riak", "juju-info", "juju-info", "server")]) |
3354 | - |
3355 | - @inlineCallbacks |
3356 | - def test_get_relation_endpoints_service_name_relation_name(self): |
3357 | - """Test getting endpoints with ``<service name:relation name>``""" |
3358 | - yield self.add_service_from_charm("wordpress") |
3359 | - self.assertEqual( |
3360 | - (yield self.service_state_manager.get_relation_endpoints( |
3361 | - "wordpress:url")), |
3362 | - [RelationEndpoint("wordpress", "http", "url", "server"), |
3363 | - RelationEndpoint("wordpress", "juju-info", "juju-info", "server")]) |
3364 | - self.assertEqual( |
3365 | - (yield self.service_state_manager.get_relation_endpoints( |
3366 | - "wordpress:db")), |
3367 | - [RelationEndpoint("wordpress", "mysql", "db", "client"), |
3368 | - RelationEndpoint("wordpress", "juju-info", "juju-info", "server")]) |
3369 | - self.assertEqual( |
3370 | - (yield self.service_state_manager.get_relation_endpoints( |
3371 | - "wordpress:cache")), |
3372 | - [RelationEndpoint("wordpress", "varnish", "cache", "client"), |
3373 | - RelationEndpoint("wordpress", "juju-info", "juju-info", "server")]) |
3374 | - yield self.add_service_from_charm("riak") |
3375 | - self.assertEqual( |
3376 | - (yield self.service_state_manager.get_relation_endpoints( |
3377 | - "riak:ring")), |
3378 | - [RelationEndpoint("riak", "riak", "ring", "peer"), |
3379 | - RelationEndpoint("riak", "juju-info", "juju-info", "server")]) |
3380 | - |
3381 | - @inlineCallbacks |
3382 | - def test_descriptor_for_services_without_charms(self): |
3383 | - """Test with services that have no corresponding charms defined""" |
3384 | - yield self.add_service("nocharm") |
3385 | - # verify we get the implicit interface |
3386 | - self.assertEqual( |
3387 | - (yield self.service_state_manager.get_relation_endpoints( |
3388 | - "nocharm")), |
3389 | - [RelationEndpoint("nocharm", |
3390 | - "juju-info", "juju-info", "server")]) |
3391 | - |
3392 | - self.assertEqual( |
3393 | - (yield self.service_state_manager.get_relation_endpoints( |
3394 | - "nocharm:nonsense")), |
3395 | - [RelationEndpoint("nocharm", |
3396 | - "juju-info", "juju-info", "server")]) |
3397 | - |
3398 | - @inlineCallbacks |
3399 | - def test_descriptor_for_missing_service(self): |
3400 | - """Test with a service that is not in the topology""" |
3401 | - yield self.assertFailure( |
3402 | - self.service_state_manager.get_relation_endpoints( |
3403 | - "notadded"), |
3404 | - ServiceStateNotFound) |
3405 | - |
3406 | - @inlineCallbacks |
3407 | - def test_bad_descriptors(self): |
3408 | - """Test that the descriptors meet the minimum naming standards""" |
3409 | - yield self.assertFailure( |
3410 | - self.service_state_manager.get_relation_endpoints("a:b:c"), |
3411 | - BadDescriptor) |
3412 | - yield self.assertFailure( |
3413 | - self.service_state_manager.get_relation_endpoints(""), |
3414 | - BadDescriptor) |
3415 | - |
3416 | - @inlineCallbacks |
3417 | - def test_join_descriptors_service_name(self): |
3418 | - """Test descriptor of the form ``<service name>`""" |
3419 | - yield self.add_service_from_charm("wordpress") |
3420 | - yield self.add_service_from_charm("mysql") |
3421 | - self.assertEqual( |
3422 | - (yield self.service_state_manager.join_descriptors( |
3423 | - "wordpress", "mysql")), |
3424 | - [(RelationEndpoint("wordpress", "mysql", "db", "client"), |
3425 | - RelationEndpoint("mysql", "mysql", "server", "server"))]) |
3426 | - # symmetric - note the pair has rotated |
3427 | - self.assertEqual( |
3428 | - (yield self.service_state_manager.join_descriptors( |
3429 | - "mysql", "wordpress")), |
3430 | - [(RelationEndpoint("mysql", "mysql", "server", "server"), |
3431 | - RelationEndpoint("wordpress", "mysql", "db", "client"))]) |
3432 | - yield self.add_service_from_charm("varnish") |
3433 | - self.assertEqual( |
3434 | - (yield self.service_state_manager.join_descriptors( |
3435 | - "wordpress", "varnish")), |
3436 | - [(RelationEndpoint("wordpress", "varnish", "cache", "client"), |
3437 | - RelationEndpoint("varnish", "varnish", "webcache", "server"))]) |
3438 | - |
3439 | - @inlineCallbacks |
3440 | - def test_join_descriptors_service_name_relation_name(self): |
3441 | - """Test joining descriptors ``<service name:relation name>``""" |
3442 | - yield self.add_service_from_charm("wordpress") |
3443 | - yield self.add_service_from_charm("mysql") |
3444 | - self.assertEqual( |
3445 | - (yield self.service_state_manager.join_descriptors( |
3446 | - "wordpress:db", "mysql")), |
3447 | - [(RelationEndpoint("wordpress", "mysql", "db", "client"), |
3448 | - RelationEndpoint("mysql", "mysql", "server", "server"))]) |
3449 | - self.assertEqual( |
3450 | - (yield self.service_state_manager.join_descriptors( |
3451 | - "mysql:server", "wordpress")), |
3452 | - [(RelationEndpoint("mysql", "mysql", "server", "server"), |
3453 | - RelationEndpoint("wordpress", "mysql", "db", "client"))]) |
3454 | - self.assertEqual( |
3455 | - (yield self.service_state_manager.join_descriptors( |
3456 | - "mysql:server", "wordpress:db")), |
3457 | - [(RelationEndpoint("mysql", "mysql", "server", "server"), |
3458 | - RelationEndpoint("wordpress", "mysql", "db", "client"))]) |
3459 | - |
3460 | - yield self.add_service_from_charm("varnish") |
3461 | - self.assertEqual( |
3462 | - (yield self.service_state_manager.join_descriptors( |
3463 | - "wordpress:cache", "varnish")), |
3464 | - [(RelationEndpoint("wordpress", "varnish", "cache", "client"), |
3465 | - RelationEndpoint("varnish", "varnish", "webcache", "server"))]) |
3466 | - self.assertEqual( |
3467 | - (yield self.service_state_manager.join_descriptors( |
3468 | - "wordpress:cache", "varnish:webcache")), |
3469 | - [(RelationEndpoint("wordpress", "varnish", "cache", "client"), |
3470 | - RelationEndpoint("varnish", "varnish", "webcache", "server"))]) |
3471 | - |
3472 | - @inlineCallbacks |
3473 | - def test_join_peer_descriptors(self): |
3474 | - """Test joining of peer relation descriptors""" |
3475 | - yield self.add_service_from_charm("riak") |
3476 | - self.assertEqual( |
3477 | - (yield self.service_state_manager.join_descriptors( |
3478 | - "riak", "riak")), |
3479 | - [(RelationEndpoint("riak", "riak", "ring", "peer"), |
3480 | - RelationEndpoint("riak", "riak", "ring", "peer"))]) |
3481 | - self.assertEqual( |
3482 | - (yield self.service_state_manager.join_descriptors( |
3483 | - "riak:ring", "riak")), |
3484 | - [(RelationEndpoint("riak", "riak", "ring", "peer"), |
3485 | - RelationEndpoint("riak", "riak", "ring", "peer"))]) |
3486 | - self.assertEqual( |
3487 | - (yield self.service_state_manager.join_descriptors( |
3488 | - "riak:ring", "riak:ring")), |
3489 | - [(RelationEndpoint("riak", "riak", "ring", "peer"), |
3490 | - RelationEndpoint("riak", "riak", "ring", "peer"))]) |
3491 | - self.assertEqual( |
3492 | - (yield self.service_state_manager.join_descriptors( |
3493 | - "riak:no-ring", "riak:ring")), |
3494 | - []) |
3495 | - |
3496 | - @inlineCallbacks |
3497 | - def test_join_descriptors_no_common_relation(self): |
3498 | - """Test joining of descriptors that do not share a relation""" |
3499 | - yield self.add_service_from_charm("mysql") |
3500 | - yield self.add_service_from_charm("riak") |
3501 | - yield self.add_service_from_charm("wordpress") |
3502 | - yield self.add_service_from_charm("varnish") |
3503 | - self.assertEqual( |
3504 | - (yield self.service_state_manager.join_descriptors( |
3505 | - "mysql", "riak")), |
3506 | - []) |
3507 | - self.assertEqual( |
3508 | - (yield self.service_state_manager.join_descriptors( |
3509 | - "mysql:server", "riak:ring")), |
3510 | - []) |
3511 | - self.assertEqual( |
3512 | - (yield self.service_state_manager.join_descriptors( |
3513 | - "varnish", "mysql")), |
3514 | - []) |
3515 | - self.assertEqual( |
3516 | - (yield self.service_state_manager.join_descriptors( |
3517 | - "riak:ring", "riak:admin")), |
3518 | - []) |
3519 | - self.assertEqual( |
3520 | - (yield self.service_state_manager.join_descriptors( |
3521 | - "riak", "wordpress")), |
3522 | - []) |
3523 | - |
3524 | - @inlineCallbacks |
3525 | - def test_join_descriptors_no_service_state(self): |
3526 | - """Test joining of nonexistent services""" |
3527 | - yield self.add_service_from_charm("wordpress") |
3528 | - yield self.assertFailure( |
3529 | - self.service_state_manager.join_descriptors("wordpress", "nosuch"), |
3530 | - ServiceStateNotFound) |
3531 | - yield self.assertFailure( |
3532 | - self.service_state_manager.join_descriptors("notyet", "nosuch"), |
3533 | - ServiceStateNotFound) |
3534 | - |
3535 | - @inlineCallbacks |
3536 | - def test_watch_services_initial_callback(self): |
3537 | - """Watch service processes initial state before returning. |
3538 | - |
3539 | - Note the callback is only executed if there is some meaningful state |
3540 | - change. |
3541 | - """ |
3542 | - results = [] |
3543 | - |
3544 | - def callback(*args): |
3545 | - results.append(True) |
3546 | - |
3547 | - yield self.service_state_manager.watch_service_states(callback) |
3548 | - yield self.add_service("wordpress") |
3549 | - yield self.poke_zk() |
3550 | - self.assertTrue(results) |
3551 | - |
3552 | - @inlineCallbacks |
3553 | - def test_watch_services_when_being_created(self): |
3554 | - """ |
3555 | - It should be possible to start watching services even |
3556 | - before they are created. In this case, the callback will |
3557 | - be made when it's actually introduced. |
3558 | - """ |
3559 | - wait_callback = [Deferred() for i in range(10)] |
3560 | - |
3561 | - calls = [] |
3562 | - |
3563 | - def watch_services(old_services, new_services): |
3564 | - calls.append((old_services, new_services)) |
3565 | - wait_callback[len(calls) - 1].callback(True) |
3566 | - |
3567 | - # Start watching. |
3568 | - self.service_state_manager.watch_service_states(watch_services) |
3569 | - |
3570 | - # Callback is still untouched. |
3571 | - self.assertEquals(calls, []) |
3572 | - |
3573 | - # Add a service, and wait for callback. |
3574 | - yield self.add_service("wordpress") |
3575 | - yield wait_callback[0] |
3576 | - |
3577 | - # The first callback must have been fired, and it must have None |
3578 | - # as the first argument because that's the first service seen. |
3579 | - self.assertEquals(len(calls), 1) |
3580 | - old_services, new_services = calls[0] |
3581 | - self.assertEquals(old_services, set()) |
3582 | - self.assertEquals(new_services, set(["wordpress"])) |
3583 | - |
3584 | - # Add a service again. |
3585 | - yield self.add_service("mysql") |
3586 | - yield wait_callback[1] |
3587 | - |
3588 | - # Now the watch callback must have been fired with two |
3589 | - # different service sets. The old one, and the new one. |
3590 | - self.assertEquals(len(calls), 2) |
3591 | - old_services, new_services = calls[1] |
3592 | - self.assertEquals(old_services, set(["wordpress"])) |
3593 | - self.assertEquals(new_services, set(["mysql", "wordpress"])) |
3594 | - |
3595 | - @inlineCallbacks |
3596 | - def test_watch_services_may_defer(self): |
3597 | - """ |
3598 | - The watch services callback may return a deferred so that it |
3599 | - performs some of its logic asynchronously. In this case, it |
3600 | - must not be called a second time before its postponed logic |
3601 | - is finished completely. |
3602 | - """ |
3603 | - wait_callback = [Deferred() for i in range(10)] |
3604 | - finish_callback = [Deferred() for i in range(10)] |
3605 | - |
3606 | - calls = [] |
3607 | - |
3608 | - def watch_services(old_services, new_services): |
3609 | - calls.append((old_services, new_services)) |
3610 | - wait_callback[len(calls) - 1].callback(True) |
3611 | - return finish_callback[len(calls) - 1] |
3612 | - |
3613 | - # Start watching. |
3614 | - self.service_state_manager.watch_service_states(watch_services) |
3615 | - |
3616 | - # Create the service. |
3617 | - yield self.add_service("wordpress") |
3618 | - |
3619 | - # Hold off until callback is started. |
3620 | - yield wait_callback[0] |
3621 | - |
3622 | - # Add another service. |
3623 | - yield self.add_service("mysql") |
3624 | - |
3625 | - # Ensure we still have a single call. |
3626 | - self.assertEquals(len(calls), 1) |
3627 | - |
3628 | - # Allow the first call to be completed, and wait on the |
3629 | - # next one. |
3630 | - finish_callback[0].callback(None) |
3631 | - yield wait_callback[1] |
3632 | - finish_callback[1].callback(None) |
3633 | - |
3634 | - # We should have the second change now. |
3635 | - self.assertEquals(len(calls), 2) |
3636 | - old_services, new_services = calls[1] |
3637 | - self.assertEquals(old_services, set(["wordpress"])) |
3638 | - self.assertEquals(new_services, set(["mysql", "wordpress"])) |
3639 | - |
3640 | - @inlineCallbacks |
3641 | - def test_watch_services_with_changing_topology(self): |
3642 | - """ |
3643 | - If the topology changes in an unrelated way, the services |
3644 | - watch callback should not be called with two equal |
3645 | - arguments. |
3646 | - """ |
3647 | - wait_callback = [Deferred() for i in range(10)] |
3648 | - |
3649 | - calls = [] |
3650 | - |
3651 | - def watch_services(old_services, new_services): |
3652 | - calls.append((old_services, new_services)) |
3653 | - wait_callback[len(calls) - 1].callback(True) |
3654 | - |
3655 | - # Start watching. |
3656 | - self.service_state_manager.watch_service_states(watch_services) |
3657 | - |
3658 | - # Callback is still untouched. |
3659 | - self.assertEquals(calls, []) |
3660 | - |
3661 | - # Add a service, and wait for callback. |
3662 | - yield self.add_service("wordpress") |
3663 | - yield wait_callback[0] |
3664 | - |
3665 | - # Now change the topology in an unrelated way. |
3666 | - yield self.machine_state_manager.add_machine_state( |
3667 | - series_constraints) |
3668 | - |
3669 | - # Add a service again. |
3670 | - yield self.add_service("mysql") |
3671 | - yield wait_callback[1] |
3672 | - |
3673 | - # But it *shouldn't* have happened. |
3674 | - self.assertEquals(len(calls), 2) |
3675 | - |
3676 | - @inlineCallbacks |
3677 | - def test_watch_service_units_initial_callback(self): |
3678 | - """Watch service unit processes initial state before returning. |
3679 | - |
3680 | - Note the callback is only executed if there is some meaningful state |
3681 | - change. |
3682 | - """ |
3683 | - results = [] |
3684 | - |
3685 | - def callback(*args): |
3686 | - results.append(True) |
3687 | - |
3688 | - service_state = yield self.add_service("wordpress") |
3689 | - yield service_state.watch_service_unit_states(callback) |
3690 | - yield service_state.add_unit_state() |
3691 | - yield self.poke_zk() |
3692 | - self.assertTrue(results) |
3693 | - |
3694 | - @inlineCallbacks |
3695 | - def test_watch_service_units_when_being_created(self): |
3696 | - """ |
3697 | - It should be possible to start watching service units even |
3698 | - before they are created. In this case, the callback will be |
3699 | - made when it's actually introduced. |
3700 | - """ |
3701 | - wait_callback = [Deferred() for i in range(10)] |
3702 | - |
3703 | - calls = [] |
3704 | - |
3705 | - def watch_service_units(old_service_units, new_service_units): |
3706 | - calls.append((old_service_units, new_service_units)) |
3707 | - wait_callback[len(calls) - 1].callback(True) |
3708 | - |
3709 | - # Start watching. |
3710 | - service_state = yield self.add_service("wordpress") |
3711 | - service_state.watch_service_unit_states(watch_service_units) |
3712 | - |
3713 | - # Callback is still untouched. |
3714 | - self.assertEquals(calls, []) |
3715 | - |
3716 | - # Add a service unit, and wait for callback. |
3717 | - yield service_state.add_unit_state() |
3718 | - |
3719 | - yield wait_callback[0] |
3720 | - |
3721 | - # The first callback must have been fired, and it must have None |
3722 | - # as the first argument because that's the first service seen. |
3723 | - self.assertEquals(len(calls), 1) |
3724 | - old_service_units, new_service_units = calls[0] |
3725 | - self.assertEquals(old_service_units, set()) |
3726 | - self.assertEquals(new_service_units, set(["wordpress/0"])) |
3727 | - |
3728 | - # Add another service unit. |
3729 | - yield service_state.add_unit_state() |
3730 | - yield wait_callback[1] |
3731 | - |
3732 | - # Now the watch callback must have been fired with two |
3733 | - # different service sets. The old one, and the new one. |
3734 | - self.assertEquals(len(calls), 2) |
3735 | - old_service_units, new_service_units = calls[1] |
3736 | - self.assertEquals(old_service_units, set(["wordpress/0"])) |
3737 | - self.assertEquals(new_service_units, set(["wordpress/0", "wordpress/1"])) |
3738 | - |
3739 | - @inlineCallbacks |
3740 | - def test_watch_service_units_may_defer(self): |
3741 | - """ |
3742 | - The watch service units callback may return a deferred so that |
3743 | - it performs some of its logic asynchronously. In this case, |
3744 | - it must not be called a second time before its postponed logic |
3745 | - is finished completely. |
3746 | - """ |
3747 | - wait_callback = [Deferred() for i in range(10)] |
3748 | - finish_callback = [Deferred() for i in range(10)] |
3749 | - |
3750 | - calls = [] |
3751 | - |
3752 | - def watch_service_units(old_service_units, new_service_units): |
3753 | - calls.append((old_service_units, new_service_units)) |
3754 | - wait_callback[len(calls) - 1].callback(True) |
3755 | - return finish_callback[len(calls) - 1] |
3756 | - |
3757 | - # Start watching. |
3758 | - service_state = yield self.add_service("wordpress") |
3759 | - service_state.watch_service_unit_states(watch_service_units) |
3760 | - |
3761 | - # Create the service unit. |
3762 | - yield service_state.add_unit_state() |
3763 | - |
3764 | - # Hold off until callback is started. |
3765 | - yield wait_callback[0] |
3766 | - |
3767 | - # Add another service unit. |
3768 | - yield service_state.add_unit_state() |
3769 | - |
3770 | - # Ensure we still have a single call. |
3771 | - self.assertEquals(len(calls), 1) |
3772 | - |
3773 | - # Allow the first call to be completed, and wait on the |
3774 | - # next one. |
3775 | - finish_callback[0].callback(None) |
3776 | - yield wait_callback[1] |
3777 | - finish_callback[1].callback(None) |
3778 | - |
3779 | - # We should have the second change now. |
3780 | - self.assertEquals(len(calls), 2) |
3781 | - old_service_units, new_service_units = calls[1] |
3782 | - self.assertEquals(old_service_units, set(["wordpress/0"])) |
3783 | - self.assertEquals( |
3784 | - new_service_units, set(["wordpress/0", "wordpress/1"])) |
3785 | - |
3786 | - @inlineCallbacks |
3787 | - def test_watch_service_units_with_changing_topology(self): |
3788 | - """ |
3789 | - If the topology changes in an unrelated way, the services |
3790 | - watch callback should not be called with two equal |
3791 | - arguments. |
3792 | - """ |
3793 | - wait_callback = [Deferred() for i in range(10)] |
3794 | - |
3795 | - calls = [] |
3796 | - |
3797 | - def watch_service_units(old_service_units, new_service_units): |
3798 | - calls.append((old_service_units, new_service_units)) |
3799 | - wait_callback[len(calls) - 1].callback(True) |
3800 | - |
3801 | - # Start watching. |
3802 | - service_state = yield self.add_service("wordpress") |
3803 | - service_state.watch_service_unit_states(watch_service_units) |
3804 | - |
3805 | - # Callback is still untouched. |
3806 | - self.assertEquals(calls, []) |
3807 | - |
3808 | - # Add a service, and wait for callback. |
3809 | - yield service_state.add_unit_state() |
3810 | - yield wait_callback[0] |
3811 | - |
3812 | - # Now change the topology in an unrelated way. |
3813 | - yield self.machine_state_manager.add_machine_state( |
3814 | - series_constraints) |
3815 | - |
3816 | - # Add a service again. |
3817 | - yield service_state.add_unit_state() |
3818 | - yield wait_callback[1] |
3819 | - |
3820 | - # But it *shouldn't* have happened. |
3821 | - self.assertEquals(len(calls), 2) |
3822 | - |
3823 | - @inlineCallbacks |
3824 | - def test_service_config_get_set(self): |
3825 | - """Validate that we can set and get service config options.""" |
3826 | - wordpress = yield self.add_service_from_charm("wordpress") |
3827 | - |
3828 | - # attempt to get the initialized service state |
3829 | - config = yield wordpress.get_config() |
3830 | - |
3831 | - # the initial state is empty |
3832 | - self.assertEqual(config, {"blog-title": "My Title"}) |
3833 | - |
3834 | - # behaves as a normal dict |
3835 | - self.assertRaises(KeyError, config.__getitem__, "missing") |
3836 | - |
3837 | - # various ways to set state |
3838 | - config.update(dict(alpha="beta", one="two")) |
3839 | - config["another"] = "value" |
3840 | - |
3841 | - # write the values |
3842 | - yield config.write() |
3843 | - |
3844 | - # we should be able to read the config and see the same values |
3845 | - # (in this case it would be the cached object) |
3846 | - config2 = yield wordpress.get_config() |
3847 | - self.assertEqual(config2, {"alpha": "beta", |
3848 | - "one": "two", |
3849 | - "another": "value", |
3850 | - "blog-title": "My Title"}) |
3851 | - |
3852 | - # now set a non-string value and recover it |
3853 | - config2["number"] = 1 |
3854 | - config2["one"] = None |
3855 | - yield config2.write() |
3856 | - |
3857 | - yield config.read() |
3858 | - self.assertEquals(config["number"], 1) |
3859 | - self.assertEquals(config["one"], None) |
3860 | - |
3861 | - @inlineCallbacks |
3862 | - def test_service_config_get_returns_new(self): |
3863 | - """Validate that we can set and get service config options.""" |
3864 | - wordpress = yield self.add_service_from_charm("wordpress") |
3865 | - |
3866 | - # attempt to get the initialized service state |
3867 | - config = yield wordpress.get_config() |
3868 | - config.update({"foo": "bar"}) |
3869 | - # Defaults come through |
3870 | - self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"}) |
3871 | - |
3872 | - config2 = yield wordpress.get_config() |
3873 | - self.assertEqual(config2, {"blog-title": "My Title"}) |
3874 | - |
3875 | - yield config.write() |
3876 | - self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"}) |
3877 | - |
3878 | - # Config2 is still empty (a different YAML State), with charm defaults. |
3879 | - self.assertEqual(config2, {"blog-title": "My Title"}) |
3880 | - |
3881 | - yield config2.read() |
3882 | - self.assertEqual(config, {"foo": "bar", "blog-title": "My Title"}) |
3883 | - |
3884 | - # The default was never written to storage. |
3885 | - data, stat = yield self.client.get( |
3886 | - "/services/%s/config" % wordpress.internal_id) |
3887 | - self.assertEqual(yaml.load(data), {"foo": "bar"}) |
3888 | - |
3889 | - @inlineCallbacks |
3890 | - def test_get_charm_state(self): |
3891 | - wordpress = yield self.add_service_from_charm("wordpress") |
3892 | - charm = yield wordpress.get_charm_state() |
3893 | - |
3894 | - self.assertEqual(charm.name, "wordpress") |
3895 | - metadata = yield charm.get_metadata() |
3896 | - self.assertEqual(metadata.summary, "Blog engine") |
3897 | + @inlineCallbacks |
3898 | + def test_add_service(self): |
3899 | + """ |
3900 | + Adding a service state should register it in zookeeper, |
3901 | + including the requested charm id. |
3902 | + """ |
3903 | + yield self.service_state_manager.add_service_state( |
3904 | + "wordpress", self.charm_state, dummy_constraints) |
3905 | + yield self.service_state_manager.add_service_state( |
3906 | + "mysql", self.charm_state, dummy_constraints) |
3907 | + children = yield self.client.get_children("/services") |
3908 | + |
3909 | + self.assertEquals(sorted(children), |
3910 | + ["service-0000000000", "service-0000000001"]) |
3911 | + |
3912 | + content, stat = yield self.client.get("/services/service-0000000000") |
3913 | + details = yaml.load(content) |
3914 | + self.assertTrue(details) |
3915 | + self.assertEquals(details.get("charm"), "local:series/dummy-1") |
3916 | + self.assertFalse(isinstance(details.get("charm"), unicode)) |
3917 | + self.assertEquals(details.get("constraints"), series_constraints.data) |
3918 | + |
3919 | + topology = yield self.get_topology() |
3920 | + self.assertEquals(topology.find_service_with_name("wordpress"), |
3921 | + "service-0000000000") |
3922 | + self.assertEquals(topology.find_service_with_name("mysql"), |
3923 | + "service-0000000001") |
3924 | + |
3925 | + @inlineCallbacks |
3926 | + def test_add_service_with_duplicated_name(self): |
3927 | + """ |
3928 | + If a service is added with a duplicated name, a meaningful |
3929 | + error should be raised. |
3930 | + """ |
3931 | + yield self.service_state_manager.add_service_state( |
3932 | + "wordpress", self.charm_state, dummy_constraints) |
3933 | + |
3934 | + try: |
3935 | + yield self.service_state_manager.add_service_state( |
3936 | + "wordpress", self.charm_state, dummy_constraints) |
3937 | + except ServiceStateNameInUse, e: |
3938 | + self.assertEquals(e.service_name, "wordpress") |
3939 | + else: |
3940 | + self.fail("Error not raised") |
3941 | + |
3942 | + @inlineCallbacks |
3943 | + def test_get_service_and_check_attributes(self): |
3944 | + """ |
3945 | + Getting a service state should be possible, and the service |
3946 | + state identification should be available through its |
3947 | + attributes. |
3948 | + """ |
3949 | + yield self.service_state_manager.add_service_state( |
3950 | + "wordpress", self.charm_state, dummy_constraints) |
3951 | + service_state = yield self.service_state_manager.get_service_state( |
3952 | + "wordpress") |
3953 | + self.assertEquals(service_state.service_name, "wordpress") |
3954 | + self.assertEquals(service_state.internal_id, "service-0000000000") |
3955 | + |
3956 | + @inlineCallbacks |
3957 | + def test_get_service_not_found(self): |
3958 | + """ |
3959 | + Getting a service state which is not available should errback |
3960 | + a meaningful error. |
3961 | + """ |
3962 | + try: |
3963 | + yield self.service_state_manager.get_service_state("wordpress") |
3964 | + except ServiceStateNotFound, e: |
3965 | + self.assertEquals(e.service_name, "wordpress") |
3966 | + else: |
3967 | + self.fail("Error not raised") |
3968 | + |
3969 | + def test_get_unit_state(self): |
3970 | + """A unit state can be retrieved by name from the service manager.""" |
3971 | + self.assertFailure(self.service_state_manager.get_unit_state( |
3972 | + "wordpress/1"), ServiceStateNotFound) |
3973 | + |
3974 | + self.assertFailure(self.service_state_manager.get_unit_state( |
3975 | + "wordpress1"), ServiceUnitStateNotFound) |
3976 | + |
3977 | + wordpress_state = yield self.service_state_manager.add_service_state( |
3978 | + "wordpress", self.charm_state, dummy_constraints) |
3979 | + |
3980 | + self.assertFailure(self.service_state_manager.get_unit_state( |
3981 | + "wordpress/1"), ServiceUnitStateNotFound) |
3982 | + |
3983 | + wordpress_unit = wordpress_state.add_unit_state() |
3984 | + |
3985 | + unit_state = yield self.service_state_manager.get_unit_state( |
3986 | + "wordpress/1") |
3987 | + |
3988 | + self.assertEqual(unit_state.internal_id, wordpress_unit.internal_id) |
3989 | + |
3990 | + @inlineCallbacks |
3991 | + def test_get_service_charm_id(self): |
3992 | + """ |
3993 | + The service state should make its respective charm id available. |
3994 | + """ |
3995 | + yield self.service_state_manager.add_service_state( |
3996 | + "wordpress", self.charm_state, dummy_constraints) |
3997 | + service_state = yield self.service_state_manager.get_service_state( |
3998 | + "wordpress") |
3999 | + charm_id = yield service_state.get_charm_id() |
4000 | + self.assertEquals(charm_id, "local:series/dummy-1") |
4001 | + |
4002 | + @inlineCallbacks |
4003 | + def test_set_service_charm_id(self): |
4004 | + """ |
4005 | + The service state should allow its charm id to be set. |
4006 | + """ |
4007 | + yield self.service_state_manager.add_service_state( |
4008 | + "wordpress", self.charm_state, dummy_constraints) |
4009 | + service_state = yield self.service_state_manager.get_service_state( |
4010 | + "wordpress") |
4011 | + yield service_state.set_charm_id("local:series/dummy-2") |
4012 | + charm_id = yield service_state.get_charm_id() |
4013 | + self.assertEquals(charm_id, "local:series/dummy-2") |
4014 | + |
4015 | + @inlineCallbacks |
4016 | + def test_get_service_constraints(self): |
4017 | + """The service state should make constraints available""" |
4018 | + initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"]) |
4019 | + yield self.service_state_manager.add_service_state( |
4020 | + "wordpress", self.charm_state, initial_constraints) |
4021 | + service_state = yield self.service_state_manager.get_service_state( |
4022 | + "wordpress") |
4023 | + constraints = yield service_state.get_constraints() |
4024 | + self.assertEquals( |
4025 | + constraints, initial_constraints.with_series("series")) |
4026 | + |
4027 | + @inlineCallbacks |
4028 | + def test_get_service_constraints_inherits(self): |
4029 | + """The service constraints should be combined with the environment's""" |
4030 | + yield self.push_env_constraints("arch=arm", "cpu=32") |
4031 | + service_constraints = dummy_cs.parse(["cpu=256"]) |
4032 | + yield self.service_state_manager.add_service_state( |
4033 | + "wordpress", self.charm_state, service_constraints) |
4034 | + service_state = yield self.service_state_manager.get_service_state( |
4035 | + "wordpress") |
4036 | + constraints = yield service_state.get_constraints() |
4037 | + expected_base = dummy_cs.parse(["arch=arm", "cpu=256"]) |
4038 | + self.assertEquals(constraints, expected_base.with_series("series")) |
4039 | + |
4040 | + @inlineCallbacks |
4041 | + def test_get_missing_service_constraints(self): |
4042 | + """ |
4043 | + Nodes created before the constraints mechanism was added should have |
4044 | + empty constraints. |
4045 | + """ |
4046 | + yield self.client.delete("/constraints") |
4047 | + yield self.service_state_manager.add_service_state( |
4048 | + "wordpress", self.charm_state, dummy_constraints) |
4049 | + service_state = yield self.service_state_manager.get_service_state( |
4050 | + "wordpress") |
4051 | + path = "/services/" + service_state.internal_id |
4052 | + node = YAMLState(self.client, path) |
4053 | + yield node.read() |
4054 | + del node["constraints"] |
4055 | + yield node.write() |
4056 | + constraints = yield service_state.get_constraints() |
4057 | + self.assertEquals(constraints.data, {}) |
4058 | + |
4059 | + @inlineCallbacks |
4060 | + def test_get_missing_unit_constraints(self): |
4061 | + """ |
4062 | + Nodes created before the constraints mechanism was added should have |
4063 | + empty constraints. |
4064 | + """ |
4065 | + yield self.service_state_manager.add_service_state( |
4066 | + "wordpress", self.charm_state, dummy_constraints) |
4067 | + service_state = yield self.service_state_manager.get_service_state( |
4068 | + "wordpress") |
4069 | + unit_state = yield service_state.add_unit_state() |
4070 | + path = "/units/" + unit_state.internal_id |
4071 | + node = YAMLState(self.client, path) |
4072 | + yield node.read() |
4073 | + del node["constraints"] |
4074 | + yield node.write() |
4075 | + constraints = yield unit_state.get_constraints() |
4076 | + self.assertEquals(constraints.data, {}) |
4077 | + |
4078 | + @inlineCallbacks |
4079 | + def test_set_service_constraints(self): |
4080 | + """The service state should make constraints available for change""" |
4081 | + initial_constraints = dummy_cs.parse(["cpu=256", "arch=amd64"]) |
4082 | + yield self.service_state_manager.add_service_state( |
4083 | + "wordpress", self.charm_state, initial_constraints) |
4084 | + service_state = yield self.service_state_manager.get_service_state( |
4085 | + "wordpress") |
4086 | + new_constraints = dummy_cs.parse(["mem=2G", "arch=arm"]) |
4087 | + yield service_state.set_constraints(new_constraints) |
4088 | + retrieved_constraints = yield service_state.get_constraints() |
4089 | + self.assertEquals( |
4090 | + retrieved_constraints, new_constraints.with_series("series")) |
4091 | + |
4092 | + @inlineCallbacks |
4093 | + def test_remove_service_state(self): |
4094 | + """ |
4095 | + A service state can be removed along with its relations, units, |
4096 | + and zookeeper state. |
4097 | + """ |
4098 | + service_state = yield self.service_state_manager.add_service_state( |
4099 | + "wordpress", self.charm_state, dummy_constraints) |
4100 | + |
4101 | + relation_state = yield self.add_relation( |
4102 | + "rel-type2", "global", [service_state, "app", "server"]) |
4103 | + |
4104 | + unit_state = yield service_state.add_unit_state() |
4105 | + machine_state = yield self.machine_state_manager.add_machine_state( |
4106 | + series_constraints) |
4107 | + yield unit_state.assign_to_machine(machine_state) |
4108 | + |
4109 | + yield self.service_state_manager.remove_service_state(service_state) |
4110 | + |
4111 | + topology = yield self.get_topology() |
4112 | + self.assertFalse(topology.has_relation(relation_state.internal_id)) |
4113 | + self.assertFalse(topology.has_service(service_state.internal_id)) |
4114 | + self.assertFalse( |
4115 | + topology.get_service_units_in_machine(machine_state.internal_id)) |
4116 | + |
4117 | + exists = yield self.client.exists( |
4118 | + "/services/%s" % service_state.internal_id) |
4119 | + self.assertFalse(exists) |
4120 | + |
4121 | + @inlineCallbacks |
4122 | + def test_add_service_unit_and_check_attributes(self): |
4123 | + """ |
4124 | + A service state should enable adding a new service unit |
4125 | + under it, and again the unit should offer attributes allowing |
4126 | + its identification. |
4127 | + """ |
4128 | + service_state0 = yield self.service_state_manager.add_service_state( |
4129 | + "wordpress", self.charm_state, dummy_constraints) |
4130 | + service_state1 = yield self.service_state_manager.add_service_state( |
4131 | + "mysql", self.charm_state, dummy_constraints) |
4132 | + |
4133 | + unit_state0 = yield service_state0.add_unit_state() |
4134 | + unit_state1 = yield service_state1.add_unit_state() |
4135 | + unit_state2 = yield service_state0.add_unit_state() |
4136 | + unit_state3 = yield service_state1.add_unit_state() |
4137 | + |
4138 | + children = yield self.client.get_children("/units") |
4139 | + self.assertEquals(sorted(children), |
4140 | + ["unit-0000000000", "unit-0000000001", |
4141 | + "unit-0000000002", "unit-0000000003"]) |
4142 | + |
4143 | + self.assertEquals(unit_state0.service_name, "wordpress") |
4144 | + self.assertEquals(unit_state0.internal_id, "unit-0000000000") |
4145 | + self.assertEquals(unit_state0.unit_name, "wordpress/0") |
4146 | + |
4147 | + self.assertEquals(unit_state1.service_name, "mysql") |
4148 | + self.assertEquals(unit_state1.internal_id, "unit-0000000001") |
4149 | + self.assertEquals(unit_state1.unit_name, "mysql/0") |
4150 | + |
4151 | + self.assertEquals(unit_state2.service_name, "wordpress") |
4152 | + self.assertEquals(unit_state2.internal_id, "unit-0000000002") |
4153 | + self.assertEquals(unit_state2.unit_name, "wordpress/1") |
4154 | + |
4155 | + self.assertEquals(unit_state3.service_name, "mysql") |
4156 | + self.assertEquals(unit_state3.internal_id, "unit-0000000003") |
4157 | + self.assertEquals(unit_state3.unit_name, "mysql/1") |
4158 | + |
4159 | + topology = yield self.get_topology() |
4160 | + |
4161 | + self.assertTrue( |
4162 | + topology.has_service_unit("service-0000000000", |
4163 | + "unit-0000000000")) |
4164 | + self.assertTrue( |
4165 | + topology.has_service_unit("service-0000000001", |
4166 | + "unit-0000000001")) |
4167 | + self.assertTrue( |
4168 | + topology.has_service_unit("service-0000000000", |
4169 | + "unit-0000000002")) |
4170 | + self.assertTrue( |
4171 | + topology.has_service_unit("service-0000000001", |
4172 | + "unit-0000000003")) |
4173 | + |
4174 | + def get_presence_path( |
4175 | + self, relation_state, relation_role, unit_state, container=None): |
4176 | + container = container.internal_id if container else None |
4177 | + presence_path = "/".join(filter(None, [ |
4178 | + "/relations", |
4179 | + relation_state.internal_id, |
4180 | + container, |
4181 | + relation_role, |
4182 | + unit_state.internal_id])) |
4183 | + return presence_path |
4184 | + |
4185 | + @inlineCallbacks |
4186 | + def test_add_service_unit_with_container(self): |
4187 | + """ |
4188 | + Validate adding units with containers specified and recovering that. |
4189 | + """ |
4190 | + mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info", |
4191 | + "server", "global") |
4192 | + logging_ep = RelationEndpoint("logging", "juju-info", "juju-info", |
4193 | + "client", "container") |
4194 | + |
4195 | + logging_charm_state = yield self.get_subordinate_charm() |
4196 | + self.assertTrue(logging_charm_state.is_subordinate()) |
4197 | + log_state = yield self.service_state_manager.add_service_state( |
4198 | + "logging", logging_charm_state, dummy_constraints) |
4199 | + mysql_state = yield self.service_state_manager.add_service_state( |
4200 | + "mysql", self.charm_state, dummy_constraints) |
4201 | + |
4202 | + relation_state, service_states = (yield |
4203 | + self.relation_state_manager.add_relation_state( |
4204 | + mysql_ep, logging_ep)) |
4205 | + |
4206 | + unit_state1 = yield mysql_state.add_unit_state() |
4207 | + unit_state0 = yield log_state.add_unit_state(container=unit_state1) |
4208 | + |
4209 | + unit_state3 = yield mysql_state.add_unit_state() |
4210 | + unit_state2 = yield log_state.add_unit_state(container=unit_state3) |
4211 | + |
4212 | + self.assertEquals((yield unit_state1.get_container()), None) |
4213 | + self.assertEquals((yield unit_state0.get_container()), unit_state1) |
4214 | + self.assertEquals((yield unit_state2.get_container()), unit_state3) |
4215 | + self.assertEquals((yield unit_state3.get_container()), None) |
4216 | + |
4217 | + for unit_state in (unit_state1, unit_state3): |
4218 | + yield unit_state.set_private_address( |
4219 | + "%s.example.com" % ( |
4220 | + unit_state.unit_name.replace("/", "-"))) |
4221 | + |
4222 | + # construct the proper relation state |
4223 | + mystate = pick_attr(service_states, relation_role="server") |
4224 | + logstate = pick_attr(service_states, relation_role="client") |
4225 | + yield logstate.add_unit_state(unit_state0) |
4226 | + yield logstate.add_unit_state(unit_state2) |
4227 | + |
4228 | + yield mystate.add_unit_state(unit_state1) |
4229 | + yield mystate.add_unit_state(unit_state3) |
4230 | + |
4231 | + @inlineCallbacks |
4232 | + def verify_container(relation_state, service_relation_state, |
4233 | + unit_state, container): |
4234 | + presence_path = self.get_presence_path( |
4235 | + relation_state, |
4236 | + service_relation_state.relation_role, |
4237 | + unit_state, |
4238 | + container) |
4239 | + |
4240 | + content, stat = yield self.client.get(presence_path) |
4241 | + self.assertTrue(stat) |
4242 | + self.assertEqual(content, '') |
4243 | + # verify the node data on the relation role nodes |
4244 | + role_path = os.path.dirname(presence_path) |
4245 | + # role path |
4246 | + content, stat = yield self.client.get(role_path) |
4247 | + self.assertTrue(stat) |
4248 | + node_info = yaml.load(content) |
4249 | + |
4250 | + self.assertEqual( |
4251 | + node_info["name"], |
4252 | + service_relation_state.relation_name) |
4253 | + self.assertEqual( |
4254 | + node_info["role"], |
4255 | + service_relation_state.relation_role) |
4256 | + |
4257 | + settings_path = os.path.dirname( |
4258 | + os.path.dirname(presence_path)) + "/settings/" + \ |
4259 | + unit_state.internal_id |
4260 | + content, stat = yield self.client.get(settings_path) |
4261 | + self.assertTrue(stat) |
4262 | + settings_info = yaml.load(content) |
4263 | + |
4264 | + # Verify that private address was set |
4265 | + # we verify the content elsewhere |
4266 | + self.assertTrue(settings_info["private-address"]) |
4267 | + |
4268 | + # verify all the units are constructed as expected |
4269 | + # first the client roles with another container |
4270 | + yield verify_container(relation_state, logstate, |
4271 | + unit_state0, unit_state1) |
4272 | + |
4273 | + yield verify_container(relation_state, logstate, |
4274 | + unit_state2, unit_state3) |
4275 | + |
4276 | + # and now the principals (which are their own relation containers) |
4277 | + yield verify_container(relation_state, logstate, |
4278 | + unit_state0, unit_state1) |
4279 | + |
4280 | + yield verify_container(relation_state, mystate, |
4281 | + unit_state1, unit_state1) |
4282 | + |
4283 | + @inlineCallbacks |
4284 | + def test_get_container_no_principal(self): |
4285 | + """Get container should handle no principal.""" |
4286 | + mysql_ep = RelationEndpoint("mysql", "juju-info", "juju-info", |
4287 | + "server", "global") |
4288 | + logging_ep = RelationEndpoint("logging", "juju-info", "juju-info", |
4289 | + "client", "container") |
4290 | + |
4291 | + logging_charm_state = yield self.get_subordinate_charm() |
4292 | + self.assertTrue(logging_charm_state.is_subordinate()) |
4293 | + log_state = yield self.service_state_manager.add_service_state( |
4294 | + "logging", logging_charm_state, dummy_constraints) |
4295 | + mysql_state = yield self.service_state_manager.add_service_state( |
4296 | + "mysql", self.charm_state, dummy_constraints) |
4297 | + |
4298 | + relation_state, service_states = (yield |
4299 | + self.relation_state_manager.add_relation_state( |
4300 | + mysql_ep, logging_ep)) |
4301 | + |
4302 | + unit_state1 = yield mysql_state.add_unit_state() |
4303 | + unit_state0 = yield log_state.add_unit_state(container=unit_state1) |
4304 | + |
4305 | + unit_state3 = yield mysql_state.add_unit_state() |
4306 | + unit_state2 = yield log_state.add_unit_state(container=unit_state3) |
4307 | + |
4308 | + self.assertEquals((yield unit_state1.get_container()), None) |
4309 | + self.assertEquals((yield unit_state0.get_container()), unit_state1) |
4310 | + self.assertEquals((yield unit_state2.get_container()), unit_state3) |
4311 | + self.assertEquals((yield unit_state3.get_container()), None) |
4312 | + |
4313 | + # now remove a principal node and test again |
4314 | + |
4315 | + yield mysql_state.remove_unit_state(unit_state1) |
4316 | + container = yield unit_state0.get_container() |
4317 | + self.assertEquals(container, None) |
4318 | + |
4319 | + # the other pair is still fine |
4320 | + self.assertEquals((yield unit_state2.get_container()), unit_state3) |
4321 | + self.assertEquals((yield unit_state3.get_container()), None) |
4322 | + |
4323 | + |
4324 | + @inlineCallbacks |
4325 | + def test_add_service_unit_with_changing_state(self): |
4326 | + """ |
4327 | + When adding a service unit, there's a chance that the |
4328 | + service will go away mid-way through. Rather than blowing |
4329 | + up randomly, a nice error should be raised. |
4330 | + """ |
4331 | + service_state = yield self.service_state_manager.add_service_state( |
4332 | + "wordpress", self.charm_state, dummy_constraints) |
4333 | + |
4334 | + yield self.remove_service(service_state.internal_id) |
4335 | + |
4336 | + d = service_state.add_unit_state() |
4337 | + yield self.assertFailure(d, StateChanged) |
4338 | + |
4339 | + @inlineCallbacks |
4340 | + def test_get_unit_names(self): |
4341 | + """A service's units names are retrievable.""" |
4342 | + service_state = yield self.service_state_manager.add_service_state( |
4343 | + "wordpress", self.charm_state, dummy_constraints) |
4344 | + |
4345 | + expected_names = [] |
4346 | + for i in range(3): |
4347 | + unit_state = yield service_state.add_unit_state() |
4348 | + expected_names.append(unit_state.unit_name) |
4349 | + |
4350 | + unit_names = yield service_state.get_unit_names() |
4351 | + self.assertEqual(unit_names, expected_names) |
4352 | + |
4353 | + @inlineCallbacks |
4354 | + def test_remove_service_unit(self): |
4355 | + """Removing a service unit removes all state associated. |
4356 | + """ |
4357 | + service_state = yield self.service_state_manager.add_service_state( |
4358 | + "wordpress", self.charm_state, dummy_constraints) |
4359 | + |
4360 | + unit_state = yield service_state.add_unit_state() |
4361 | + |
4362 | + # Assign to a machine |
4363 | + machine_state = yield self.machine_state_manager.add_machine_state( |
4364 | + series_constraints) |
4365 | + yield unit_state.assign_to_machine(machine_state) |
4366 | + # Connect a unit agent |
4367 | + yield unit_state.connect_agent() |
4368 | + |
4369 | + # Now try and destroy it. |
4370 | + yield service_state.remove_unit_state(unit_state) |
4371 | + |
4372 | + # Verify destruction. |
4373 | + topology = yield self.get_topology() |
4374 | + self.assertTrue(topology.has_service(service_state.internal_id)) |
4375 | + self.assertFalse( |
4376 | + topology.has_service_unit( |
4377 | + service_state.internal_id, unit_state.internal_id)) |
4378 | + |
4379 | + exists = yield self.client.exists("/units/%s" % unit_state.internal_id) |
4380 | + self.assertFalse(exists) |
4381 | + |
4382 | + def test_remove_service_unit_nonexistant(self): |
4383 | + """Removing a non existant service unit, is fine.""" |
4384 | + |
4385 | + service_state = yield self.service_state_manager.add_service_state( |
4386 | + "wordpress", self.charm_state, dummy_constraints) |
4387 | + unit_state = yield service_state.add_unit_state() |
4388 | + yield service_state.remove_unit_state(unit_state) |
4389 | + yield service_state.remove_unit_state(unit_state) |
4390 | + |
4391 | + @inlineCallbacks |
4392 | + def test_get_all_service_states(self): |
4393 | + services = yield self.service_state_manager.get_all_service_states() |
4394 | + self.assertFalse(services) |
4395 | + |
4396 | + yield self.service_state_manager.add_service_state( |
4397 | + "wordpress", self.charm_state, dummy_constraints) |
4398 | + services = yield self.service_state_manager.get_all_service_states() |
4399 | + self.assertEquals(len(services), 1) |
4400 | + |
4401 | + yield self.service_state_manager.add_service_state( |
4402 | + "mysql", self.charm_state, dummy_constraints) |
4403 | + services = yield self.service_state_manager.get_all_service_states() |
4404 | + self.assertEquals(len(services), 2) |
4405 | + |
4406 | + @inlineCallbacks |
4407 | + def test_get_service_unit(self): |
4408 | + """ |
4409 | + Getting back service units should be possible using the |
4410 | + user-oriented id. |
4411 | + """ |
4412 | + service_state0 = yield self.service_state_manager.add_service_state( |
4413 | + "wordpress", self.charm_state, dummy_constraints) |
4414 | + service_state1 = yield self.service_state_manager.add_service_state( |
4415 | + "mysql", self.charm_state, dummy_constraints) |
4416 | + |
4417 | + yield service_state0.add_unit_state() |
4418 | + yield service_state1.add_unit_state() |
4419 | + yield service_state0.add_unit_state() |
4420 | + yield service_state1.add_unit_state() |
4421 | + |
4422 | + unit_state0 = yield service_state0.get_unit_state("wordpress/0") |
4423 | + unit_state1 = yield service_state1.get_unit_state("mysql/0") |
4424 | + unit_state2 = yield service_state0.get_unit_state("wordpress/1") |
4425 | + unit_state3 = yield service_state1.get_unit_state("mysql/1") |
4426 | + |
4427 | + self.assertEquals(unit_state0.internal_id, "unit-0000000000") |
4428 | + self.assertEquals(unit_state1.internal_id, "unit-0000000001") |
4429 | + self.assertEquals(unit_state2.internal_id, "unit-0000000002") |
4430 | + self.assertEquals(unit_state3.internal_id, "unit-0000000003") |
4431 | + |
4432 | + self.assertEquals(unit_state0.unit_name, "wordpress/0") |
4433 | + self.assertEquals(unit_state1.unit_name, "mysql/0") |
4434 | + self.assertEquals(unit_state2.unit_name, "wordpress/1") |
4435 | + self.assertEquals(unit_state3.unit_name, "mysql/1") |
4436 | + |
4437 | + @inlineCallbacks |
4438 | + def test_get_all_unit_states(self): |
4439 | + service_state0 = yield self.service_state_manager.add_service_state( |
4440 | + "wordpress", self.charm_state, dummy_constraints) |
4441 | + service_state1 = yield self.service_state_manager.add_service_state( |
4442 | + "mysql", self.charm_state, dummy_constraints) |
4443 | + |
4444 | + yield service_state0.add_unit_state() |
4445 | + yield service_state1.add_unit_state() |
4446 | + yield service_state0.add_unit_state() |
4447 | + yield service_state1.add_unit_state() |
4448 | + |
4449 | + unit_state0 = yield service_state0.get_unit_state("wordpress/0") |
4450 | + unit_state1 = yield service_state1.get_unit_state("mysql/0") |
4451 | + unit_state2 = yield service_state0.get_unit_state("wordpress/1") |
4452 | + unit_state3 = yield service_state1.get_unit_state("mysql/1") |
4453 | + |
4454 | + wordpress_units = yield service_state0.get_all_unit_states() |
4455 | + self.assertEquals( |
4456 | + set(wordpress_units), set((unit_state0, unit_state2))) |
4457 | + |
4458 | + mysql_units = yield service_state1.get_all_unit_states() |
4459 | + self.assertEquals(set(mysql_units), set((unit_state1, unit_state3))) |
4460 | + |
4461 | + @inlineCallbacks |
4462 | + def test_get_all_unit_states_with_changing_state(self): |
4463 | + """ |
4464 | + When getting the service unit states, there's a chance that |
4465 | + the service will go away mid-way through. Rather than blowing |
4466 | + up randomly, a nice error should be raised. |
4467 | + """ |
4468 | + service_state = yield self.service_state_manager.add_service_state( |
4469 | + "wordpress", self.charm_state, dummy_constraints) |
4470 | + yield service_state.add_unit_state() |
4471 | + unit_state = (yield service_state.get_all_unit_states())[0] |
4472 | + self.assertEqual(unit_state.unit_name, "wordpress/0") |
4473 | + yield self.remove_service(service_state.internal_id) |
4474 | + yield self.assertFailure( |
4475 | + service_state.get_all_unit_states(), StateChanged) |
4476 | + |
4477 | + @inlineCallbacks |
4478 | + def test_set_functions(self): |
4479 | + wordpress = yield self.service_state_manager.add_service_state( |
4480 | + "wordpress", self.charm_state, dummy_constraints) |
4481 | + mysql = yield self.service_state_manager.add_service_state( |
4482 | + "mysql", self.charm_state, dummy_constraints) |
4483 | + |
4484 | + s1 = yield self.service_state_manager.get_service_state( |
4485 | + "wordpress") |
4486 | + s2 = yield self.service_state_manager.get_service_state( |
4487 | + "mysql") |
4488 | + self.assertEquals(hash(s1), hash(wordpress)) |
4489 | + self.assertEquals(hash(s2), hash(mysql)) |
4490 | + |
4491 | + self.assertNotEqual(s1, object()) |
4492 | + self.assertNotEqual(s1, s2) |
4493 | + |
4494 | + self.assertEquals(s1, wordpress) |
4495 | + self.assertEquals(s2, mysql) |
4496 | + |
4497 | + us0 = yield wordpress.add_unit_state() |
4498 | + us1 = yield wordpress.add_unit_state() |
4499 | + |
4500 | + unit_state0 = yield wordpress.get_unit_state("wordpress/0") |
4501 | + unit_state1 = yield wordpress.get_unit_state("wordpress/1") |
4502 | + |
4503 | + self.assertEquals(us0, unit_state0) |
4504 | + self.assertEquals(us1, unit_state1) |
4505 | + self.assertEquals(hash(us1), hash(unit_state1)) |
4506 | + |
4507 | + self.assertNotEqual(us0, object()) |
4508 | + self.assertNotEqual(us0, us1) |
4509 | + |
4510 | + @inlineCallbacks |
4511 | + def test_get_service_unit_not_found(self): |
4512 | + """ |
4513 | + Attempting to retrieve a non-existent service unit should |
4514 | + result in an errback. |
4515 | + """ |
4516 | + service_state0 = yield self.service_state_manager.add_service_state( |
4517 | + "wordpress", self.charm_state, dummy_constraints) |
4518 | + service_state1 = yield self.service_state_manager.add_service_state( |
4519 | + "mysql", self.charm_state, dummy_constraints) |
4520 | + |
4521 | + # Add some state in a different service to make it a little |
4522 | + # bit more prone to breaking in case of errors. |
4523 | + yield service_state1.add_unit_state() |
4524 | + |
4525 | + try: |
4526 | + yield service_state0.get_unit_state("wordpress/0") |
4527 | + except ServiceUnitStateNotFound, e: |
4528 | + self.assertEquals(e.unit_name, "wordpress/0") |
4529 | + else: |
4530 | + self.fail("Error not raised") |
4531 | + |
4532 | + @inlineCallbacks |
4533 | + def test_get_set_public_address(self): |
4534 | + service_state = yield self.service_state_manager.add_service_state( |
4535 | + "wordpress", self.charm_state, dummy_constraints) |
4536 | + unit_state = yield service_state.add_unit_state() |
4537 | + self.assertEqual((yield unit_state.get_public_address()), None) |
4538 | + yield unit_state.set_public_address("example.foobar.com") |
4539 | + yield self.assertEqual( |
4540 | + (yield unit_state.get_public_address()), |
4541 | + "example.foobar.com") |
4542 | + |
4543 | + @inlineCallbacks |
4544 | + def test_get_set_private_address(self): |
4545 | + service_state = yield self.service_state_manager.add_service_state( |
4546 | + "wordpress", self.charm_state, dummy_constraints) |
4547 | + unit_state = yield service_state.add_unit_state() |
4548 | + self.assertEqual((yield unit_state.get_private_address()), None) |
4549 | + yield unit_state.set_private_address("example.local") |
4550 | + yield self.assertEqual( |
4551 | + (yield unit_state.get_private_address()), |
4552 | + "example.local") |
4553 | + |
4554 | + @inlineCallbacks |
4555 | + def test_get_service_unit_with_changing_state(self): |
4556 | + """ |
4557 | + If a service is removed during operation, get_service_unit() |
4558 | + should raise a nice error. |
4559 | + """ |
4560 | + service_state = yield self.service_state_manager.add_service_state( |
4561 | + "wordpress", self.charm_state, dummy_constraints) |
4562 | + |
4563 | + yield self.remove_service(service_state.internal_id) |
4564 | + |
4565 | + d = service_state.get_unit_state("wordpress/0") |
4566 | + yield self.assertFailure(d, StateChanged) |
4567 | + |
4568 | + @inlineCallbacks |
4569 | + def test_get_service_unit_with_bad_service_name(self): |
4570 | + """ |
4571 | + Service unit names contain a service name embedded into |
4572 | + them. The service name requested when calling get_unit_state() |
4573 | + must match that of the object being used. |
4574 | + """ |
4575 | + service_state0 = yield self.service_state_manager.add_service_state( |
4576 | + "wordpress", self.charm_state, dummy_constraints) |
4577 | + service_state1 = yield self.service_state_manager.add_service_state( |
4578 | + "mysql", self.charm_state, dummy_constraints) |
4579 | + |
4580 | + # Add some state in a different service to make it a little |
4581 | + # bit more prone to breaking in case of errors. |
4582 | + yield service_state1.add_unit_state() |
4583 | + |
4584 | + try: |
4585 | + yield service_state0.get_unit_state("mysql/0") |
4586 | + except BadServiceStateName, e: |
4587 | + self.assertEquals(e.expected_name, "wordpress") |
4588 | + self.assertEquals(e.obtained_name, "mysql") |
4589 | + else: |
4590 | + self.fail("Error not raised") |
4591 | + |
4592 | + @inlineCallbacks |
4593 | + def test_assign_unit_to_machine(self): |
4594 | + service_state = yield self.service_state_manager.add_service_state( |
4595 | + "wordpress", self.charm_state, dummy_constraints) |
4596 | + unit_state = yield service_state.add_unit_state() |
4597 | + machine_state = yield self.machine_state_manager.add_machine_state( |
4598 | + series_constraints) |
4599 | + |
4600 | + yield unit_state.assign_to_machine(machine_state) |
4601 | + |
4602 | + topology = yield self.get_topology() |
4603 | + |
4604 | + self.assertEquals( |
4605 | + topology.get_service_unit_machine(service_state.internal_id, |
4606 | + unit_state.internal_id), |
4607 | + machine_state.internal_id) |
4608 | + |
4609 | + @inlineCallbacks |
4610 | + def test_assign_unit_to_machine_with_changing_state(self): |
4611 | + service_state = yield self.service_state_manager.add_service_state( |
4612 | + "wordpress", self.charm_state, dummy_constraints) |
4613 | + unit_state = yield service_state.add_unit_state() |
4614 | + machine_state = yield self.machine_state_manager.add_machine_state( |
4615 | + series_constraints) |
4616 | + |
4617 | + yield self.remove_service_unit(service_state.internal_id, |
4618 | + unit_state.internal_id) |
4619 | + |
4620 | + d = unit_state.assign_to_machine(machine_state) |
4621 | + yield self.assertFailure(d, StateChanged) |
4622 | + |
4623 | + yield self.remove_service(service_state.internal_id) |
4624 | + |
4625 | + d = unit_state.assign_to_machine(machine_state) |
4626 | + yield self.assertFailure(d, StateChanged) |
4627 | + |
4628 | + @inlineCallbacks |
4629 | + def test_unassign_unit_from_machine(self): |
4630 | + service_state = yield self.service_state_manager.add_service_state( |
4631 | + "wordpress", self.charm_state, dummy_constraints) |
4632 | + unit_state = yield service_state.add_unit_state() |
4633 | + machine_state = yield self.machine_state_manager.add_machine_state( |
4634 | + series_constraints) |
4635 | + |
4636 | + yield unit_state.assign_to_machine(machine_state) |
4637 | + yield unit_state.unassign_from_machine() |
4638 | + |
4639 | + topology = yield self.get_topology() |
4640 | + |
4641 | + self.assertEquals( |
4642 | + topology.get_service_unit_machine(service_state.internal_id, |
4643 | + unit_state.internal_id), |
4644 | + None) |
4645 | + |
4646 | + @inlineCallbacks |
4647 | + def test_get_set_clear_resolved(self): |
4648 | + """The a unit can be set to resolved to mark a future transition, with |
4649 | + an optional retry flag.""" |
4650 | + |
4651 | + unit_state = yield self.get_unit_state() |
4652 | + |
4653 | + self.assertIdentical((yield unit_state.get_resolved()), None) |
4654 | + yield unit_state.set_resolved(NO_HOOKS) |
4655 | + |
4656 | + yield self.assertFailure( |
4657 | + unit_state.set_resolved(NO_HOOKS), |
4658 | + ServiceUnitResolvedAlreadyEnabled) |
4659 | + yield self.assertEqual( |
4660 | + |
4661 | + (yield unit_state.get_resolved()), {"retry": NO_HOOKS}) |
4662 | + |
4663 | + yield unit_state.clear_resolved() |
4664 | + self.assertIdentical((yield unit_state.get_resolved()), None) |
4665 | + yield unit_state.clear_resolved() |
4666 | + |
4667 | + yield self.assertFailure(unit_state.set_resolved(None), ValueError) |
4668 | + |
4669 | + @inlineCallbacks |
4670 | + def test_watch_resolved(self): |
4671 | + """A unit resolved watch can be instituted on a permanent basis.""" |
4672 | + unit_state = yield self.get_unit_state() |
4673 | + |
4674 | + results = [] |
4675 | + |
4676 | + def callback(value): |
4677 | + results.append(value) |
4678 | + |
4679 | + unit_state.watch_resolved(callback) |
4680 | + yield unit_state.set_resolved(RETRY_HOOKS) |
4681 | + yield unit_state.clear_resolved() |
4682 | + yield unit_state.set_resolved(NO_HOOKS) |
4683 | + |
4684 | + yield self.poke_zk() |
4685 | + |
4686 | + self.assertEqual(len(results), 4) |
4687 | + self.assertIdentical(results.pop(0), False) |
4688 | + self.assertEqual(results.pop(0).type_name, "created") |
4689 | + self.assertEqual(results.pop(0).type_name, "deleted") |
4690 | + self.assertEqual(results.pop(0).type_name, "created") |
4691 | + |
4692 | + self.assertEqual( |
4693 | + (yield unit_state.get_resolved()), |
4694 | + {"retry": NO_HOOKS}) |
4695 | + |
4696 | + @inlineCallbacks |
4697 | + def test_watch_resolved_processes_current_state(self): |
4698 | + """The watch method processes the current state before returning.""" |
4699 | + unit_state = yield self.get_unit_state() |
4700 | + |
4701 | + results = [] |
4702 | + |
4703 | + @inlineCallbacks |
4704 | + def callback(value): |
4705 | + results.append((yield unit_state.get_resolved())) |
4706 | + |
4707 | + yield unit_state.watch_resolved(callback) |
4708 | + self.assertTrue(results) |
4709 | + |
4710 | + @inlineCallbacks |
4711 | + def test_stop_watch_resolved(self): |
4712 | + """A unit resolved watch can be instituted on a permanent basis. |
4713 | + |
4714 | + However the callback can raise StopWatcher at anytime to stop the watch |
4715 | + """ |
4716 | + unit_state = yield self.get_unit_state() |
4717 | + |
4718 | + results = [] |
4719 | + |
4720 | + def callback(value): |
4721 | + results.append(value) |
4722 | + if len(results) == 1: |
4723 | + raise StopWatcher() |
4724 | + if len(results) == 3: |
4725 | + raise StopWatcher() |
4726 | + |
4727 | + unit_state.watch_resolved(callback) |
4728 | + yield unit_state.set_resolved(RETRY_HOOKS) |
4729 | + yield unit_state.clear_resolved() |
4730 | + yield self.poke_zk() |
4731 | + |
4732 | + unit_state.watch_resolved(callback) |
4733 | + yield unit_state.set_resolved(NO_HOOKS) |
4734 | + yield unit_state.clear_resolved() |
4735 | + |
4736 | + yield self.poke_zk() |
4737 | + |
4738 | + self.assertEqual(len(results), 3) |
4739 | + self.assertIdentical(results.pop(0), False) |
4740 | + self.assertIdentical(results.pop(0), False) |
4741 | + self.assertEqual(results.pop(0).type_name, "created") |
4742 | + |
4743 | + self.assertEqual( |
4744 | + (yield unit_state.get_resolved()), None) |
4745 | + |
4746 | + @inlineCallbacks |
4747 | + def test_get_set_clear_relation_resolved(self): |
4748 | + """The a unit's realtions can be set to resolved to mark a |
4749 | + future transition, with an optional retry flag.""" |
4750 | + |
4751 | + unit_state = yield self.get_unit_state() |
4752 | + |
4753 | + self.assertIdentical((yield unit_state.get_relation_resolved()), None) |
4754 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
4755 | + |
4756 | + # Trying to set a conflicting raises an error |
4757 | + yield self.assertFailure( |
4758 | + unit_state.set_relation_resolved({"0": NO_HOOKS}), |
4759 | + ServiceUnitRelationResolvedAlreadyEnabled) |
4760 | + |
4761 | + # Doing the same thing is fine |
4762 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}), |
4763 | + |
4764 | + # Its fine to put in new values |
4765 | + yield unit_state.set_relation_resolved({"21": RETRY_HOOKS}) |
4766 | + yield self.assertEqual( |
4767 | + (yield unit_state.get_relation_resolved()), |
4768 | + {"0": RETRY_HOOKS, "21": RETRY_HOOKS}) |
4769 | + |
4770 | + yield unit_state.clear_relation_resolved() |
4771 | + self.assertIdentical((yield unit_state.get_relation_resolved()), None) |
4772 | + yield unit_state.clear_relation_resolved() |
4773 | + |
4774 | + yield self.assertFailure( |
4775 | + unit_state.set_relation_resolved(True), ValueError) |
4776 | + yield self.assertFailure( |
4777 | + unit_state.set_relation_resolved(None), ValueError) |
4778 | + |
4779 | + @inlineCallbacks |
4780 | + def test_watch_relation_resolved(self): |
4781 | + """A unit resolved watch can be instituted on a permanent basis.""" |
4782 | + unit_state = yield self.get_unit_state() |
4783 | + |
4784 | + results = [] |
4785 | + |
4786 | + def callback(value): |
4787 | + results.append(value) |
4788 | + |
4789 | + unit_state.watch_relation_resolved(callback) |
4790 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
4791 | + yield unit_state.clear_relation_resolved() |
4792 | + yield unit_state.set_relation_resolved({"0": NO_HOOKS}) |
4793 | + |
4794 | + yield self.poke_zk() |
4795 | + |
4796 | + self.assertEqual(len(results), 4) |
4797 | + self.assertIdentical(results.pop(0), False) |
4798 | + self.assertEqual(results.pop(0).type_name, "created") |
4799 | + self.assertEqual(results.pop(0).type_name, "deleted") |
4800 | + self.assertEqual(results.pop(0).type_name, "created") |
4801 | + |
4802 | + self.assertEqual( |
4803 | + (yield unit_state.get_relation_resolved()), |
4804 | + {"0": NO_HOOKS}) |
4805 | + |
4806 | + @inlineCallbacks |
4807 | + def test_watch_relation_resolved_processes_current_state(self): |
4808 | + """The watch method returns only after processing the current state.""" |
4809 | + unit_state = yield self.get_unit_state() |
4810 | + |
4811 | + results = [] |
4812 | + |
4813 | + @inlineCallbacks |
4814 | + def callback(value): |
4815 | + results.append((yield unit_state.get_relation_resolved())) |
4816 | + yield unit_state.watch_relation_resolved(callback) |
4817 | + self.assertTrue(results) |
4818 | + |
4819 | + @inlineCallbacks |
4820 | + def test_stop_watch_relation_resolved(self): |
4821 | + """A unit resolved watch can be instituted on a permanent basis.""" |
4822 | + unit_state = yield self.get_unit_state() |
4823 | + |
4824 | + results = [] |
4825 | + |
4826 | + def callback(value): |
4827 | + results.append(value) |
4828 | + |
4829 | + if len(results) == 1: |
4830 | + raise StopWatcher() |
4831 | + |
4832 | + if len(results) == 3: |
4833 | + raise StopWatcher() |
4834 | + |
4835 | + unit_state.watch_relation_resolved(callback) |
4836 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
4837 | + yield unit_state.clear_relation_resolved() |
4838 | + yield self.poke_zk() |
4839 | + self.assertEqual(len(results), 1) |
4840 | + |
4841 | + unit_state.watch_relation_resolved(callback) |
4842 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
4843 | + yield unit_state.clear_relation_resolved() |
4844 | + yield self.poke_zk() |
4845 | + self.assertEqual(len(results), 3) |
4846 | + self.assertIdentical(results.pop(0), False) |
4847 | + self.assertIdentical(results.pop(0), False) |
4848 | + self.assertEqual(results.pop(0).type_name, "created") |
4849 | + |
4850 | + self.assertEqual( |
4851 | + (yield unit_state.get_relation_resolved()), None) |
4852 | + |
4853 | + @inlineCallbacks |
4854 | + def test_watch_resolved_slow_callback(self): |
4855 | + """A slow watch callback is still invoked serially.""" |
4856 | + unit_state = yield self.get_unit_state() |
4857 | + |
4858 | + callbacks = [Deferred() for i in range(5)] |
4859 | + results = [] |
4860 | + contents = [] |
4861 | + |
4862 | + @inlineCallbacks |
4863 | + def watch(value): |
4864 | + results.append(value) |
4865 | + yield callbacks[len(results) - 1] |
4866 | + contents.append((yield unit_state.get_resolved())) |
4867 | + |
4868 | + callbacks[0].callback(True) |
4869 | + yield unit_state.watch_resolved(watch) |
4870 | + |
4871 | + # These get collapsed into a single event |
4872 | + yield unit_state.set_resolved(RETRY_HOOKS) |
4873 | + yield unit_state.clear_resolved() |
4874 | + yield self.poke_zk() |
4875 | + |
4876 | + # Verify the callback hasn't completed |
4877 | + self.assertEqual(len(results), 2) |
4878 | + self.assertEqual(len(contents), 1) |
4879 | + |
4880 | + # Let it finish |
4881 | + callbacks[1].callback(True) |
4882 | + yield self.poke_zk() |
4883 | + |
4884 | + # Verify result counts |
4885 | + self.assertEqual(len(results), 3) |
4886 | + self.assertEqual(len(contents), 2) |
4887 | + |
4888 | + # Verify result values. Even though we have created event, the |
4889 | + # setting retrieved shows the hook is not enabled. |
4890 | + self.assertEqual(results[-1].type_name, "deleted") |
4891 | + self.assertEqual(contents[-1], None) |
4892 | + |
4893 | + yield unit_state.set_resolved(NO_HOOKS) |
4894 | + callbacks[2].callback(True) |
4895 | + yield self.poke_zk() |
4896 | + |
4897 | + self.assertEqual(len(results), 4) |
4898 | + self.assertEqual(contents[-1], {"retry": NO_HOOKS}) |
4899 | + |
4900 | + # Clear out any pending activity. |
4901 | + yield self.poke_zk() |
4902 | + |
4903 | + @inlineCallbacks |
4904 | + def test_watch_relation_resolved_slow_callback(self): |
4905 | + """A slow watch callback is still invoked serially.""" |
4906 | + unit_state = yield self.get_unit_state() |
4907 | + |
4908 | + callbacks = [Deferred() for i in range(5)] |
4909 | + results = [] |
4910 | + contents = [] |
4911 | + |
4912 | + @inlineCallbacks |
4913 | + def watch(value): |
4914 | + results.append(value) |
4915 | + yield callbacks[len(results) - 1] |
4916 | + contents.append((yield unit_state.get_relation_resolved())) |
4917 | + |
4918 | + callbacks[0].callback(True) |
4919 | + yield unit_state.watch_relation_resolved(watch) |
4920 | + |
4921 | + # These get collapsed into a single event |
4922 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
4923 | + yield unit_state.clear_relation_resolved() |
4924 | + yield self.poke_zk() |
4925 | + |
4926 | + # Verify the callback hasn't completed |
4927 | + self.assertEqual(len(results), 2) |
4928 | + self.assertEqual(len(contents), 1) |
4929 | + |
4930 | + # Let it finish |
4931 | + callbacks[1].callback(True) |
4932 | + yield self.poke_zk() |
4933 | + |
4934 | + # Verify result counts |
4935 | + self.assertEqual(len(results), 3) |
4936 | + self.assertEqual(len(contents), 2) |
4937 | + |
4938 | + # Verify result values. Even though we have created event, the |
4939 | + # setting retrieved shows the hook is not enabled. |
4940 | + self.assertEqual(results[-1].type_name, "deleted") |
4941 | + self.assertEqual(contents[-1], None) |
4942 | + |
4943 | + yield unit_state.set_relation_resolved({"0": RETRY_HOOKS}) |
4944 | + callbacks[2].callback(True) |
4945 | + yield self.poke_zk() |
4946 | + |
4947 | + self.assertEqual(len(results), 4) |
4948 | + self.assertEqual(contents[-1], {"0": RETRY_HOOKS}) |
4949 | + |
4950 | + # Clear out any pending activity. |
4951 | + yield self.poke_zk() |
4952 | + |
4953 | + @inlineCallbacks |
4954 | + def test_set_and_clear_upgrade_flag(self): |
4955 | + """An upgrade flag can be set on a unit.""" |
4956 | + |
4957 | + # Defaults to false |
4958 | + unit_state = yield self.get_unit_state() |
4959 | + upgrade_flag = yield unit_state.get_upgrade_flag() |
4960 | + self.assertEqual(upgrade_flag, False) |
4961 | + |
4962 | + # Can be set |
4963 | + yield unit_state.set_upgrade_flag() |
4964 | + upgrade_flag = yield unit_state.get_upgrade_flag() |
4965 | + self.assertEqual(upgrade_flag, {'force': False}) |
4966 | + |
4967 | + # Attempting to set multiple times is an error if the values |
4968 | + # differ. |
4969 | + yield self.assertFailure( |
4970 | + unit_state.set_upgrade_flag(force=True), |
4971 | + ServiceUnitUpgradeAlreadyEnabled) |
4972 | + self.assertEqual(upgrade_flag, {'force': False}) |
4973 | + |
4974 | + # Can be cleared |
4975 | + yield unit_state.clear_upgrade_flag() |
4976 | + upgrade_flag = yield unit_state.get_upgrade_flag() |
4977 | + self.assertEqual(upgrade_flag, False) |
4978 | + |
4979 | + # Can be cleared multiple times |
4980 | + yield unit_state.clear_upgrade_flag() |
4981 | + upgrade_flag = yield unit_state.get_upgrade_flag() |
4982 | + self.assertEqual(upgrade_flag, False) |
4983 | + |
4984 | + # A empty node present is not problematic |
4985 | + yield self.client.create(unit_state._upgrade_flag_path, "") |
4986 | + upgrade_flag = yield unit_state.get_upgrade_flag() |
4987 | + self.assertEqual(upgrade_flag, False) |
4988 | + |
4989 | + yield unit_state.set_upgrade_flag(force=True) |
4990 | + upgrade_flag = yield unit_state.get_upgrade_flag() |
4991 | + self.assertEqual(upgrade_flag, {"force": True}) |
4992 | + |
4993 | + @inlineCallbacks |
4994 | + def test_watch_upgrade_flag_once(self): |
4995 | + """An upgrade watch can be set to notified of presence and changes.""" |
4996 | + unit_state = yield self.get_unit_state() |
4997 | + yield unit_state.set_upgrade_flag() |
4998 | + |
4999 | + results = [] |
5000 | + |
Reviewers: mp+100489_ code.launchpad. net,
Message:
Please take a look.
Description:
juju/control should be aware of subordinates
Includes support for add-unit, deploy, status, remove-unit
https:/ /code.launchpad .net/~bcsaller/ juju/subordinat e-control- status/ +merge/ 100489
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/5970068/
Affected files: add_unit. py deploy. py remove_ unit.py status. py tests/test_ add_unit. py tests/test_ deploy. py tests/test_ remove_ unit.py tests/test_ status. py tests/test_ charm.py tests/test_ lifecycle. py
A [revision details]
M juju/control/
M juju/control/
M juju/control/
M juju/control/
M juju/control/
M juju/control/
M juju/control/
M juju/control/
M juju/state/
M juju/unit/