125
120
return (services, units)
129
def collect(scope, machine_provider, client, log):
130
"""Extract status information into nested dicts for rendering.
132
`scope`: an optional list of name specifiers. Globbing based
133
wildcards supported. Defaults to all units, services and
136
`machine_provider`: machine provider for the environment
138
`client`: ZK client connection
140
`log`: a Python stdlib logger.
142
service_manager = ServiceStateManager(client)
143
relation_manager = RelationStateManager(client)
144
machine_manager = MachineStateManager(client)
145
charm_manager = CharmStateManager(client)
149
state = dict(services=service_data, machines=machine_data)
151
seen_machines = set()
152
filter_services, filter_units = digest_scope(scope)
154
services = yield service_manager.get_all_service_states()
155
for service in services:
156
if len(filter_services):
158
for filter_service in filter_services:
159
if fnmatch(service.service_name, filter_service):
123
class StatusCommand(object):
124
def __init__(self, client, provider, log):
126
Callable status command object.
128
`client`: ZK client connection
129
`provider`: machine provider for the environment
130
`log`: a Python stdlib logger.
134
self.provider = provider
137
self.service_manager = ServiceStateManager(client)
138
self.relation_manager = RelationStateManager(client)
139
self.machine_manager = MachineStateManager(client)
140
self.charm_manager = CharmStateManager(client)
143
def _reset(self, scope=None):
145
# self.state is assembled by the various process methods
146
# intermediate access to state is made more convenient
147
# using these references to its internals.
148
self.service_data = {} # service name: service info
149
self.machine_data = {} # machine id: machine state
150
self.unit_data = {} # unit_name :unit_info
152
# used in collecting subordinate (which are added to state in a two
154
self.subordinates = {} # service : set(principal service names)
156
self.state = dict(services=self.service_data,
157
machines=self.machine_data)
160
self.seen_machines = set()
161
self.filter_services, self.filter_units = digest_scope(scope)
164
def __call__(self, scope=None):
165
"""Extract status information into nested dicts for rendering.
167
`scope`: an optional list of name specifiers. Globbing based wildcards
168
supported. Defaults to all units, services and relations.
173
# Pass 1 Gather Data (including principals and subordinates)
174
# this builds unit info and container relationships
175
# which is assembled in pass 2 below
176
yield self._process_services()
178
# Pass 2: Nest information according to principal/subordinates
180
self._process_subordinates()
182
yield self._process_machines()
184
returnValue(self.state)
187
def _process_services(self):
189
For each service gather the following information::
193
exposed: <expose boolean>
195
<relation info -- see _process_relations>
197
<unit info -- see _process_units>
199
services = yield self.service_manager.get_all_service_states()
200
for service in services:
201
if len(self.filter_services):
203
for filter_service in self.filter_services:
204
if fnmatch(service.service_name, filter_service):
209
yield self._process_service(service)
212
def _process_service(self, service):
214
Gather the service info (described in _process_services).
216
`service`: ServiceState instance
166
219
relation_data = {}
220
service_data = self.service_data
168
222
charm_id = yield service.get_charm_id()
169
charm = yield charm_manager.get_charm_state(charm_id)
171
service_data[service.service_name] = dict(units=unit_data,
173
relations=relation_data)
174
exposed = yield service.get_exposed_flag()
176
service_data[service.service_name].update(exposed=exposed)
223
charm = yield self.charm_manager.get_charm_state(charm_id)
225
service_data[service.service_name] = (
228
relations=relation_data))
230
if (yield service.is_subordinate()):
231
service_data[service.service_name]["subordinate"] = True
233
yield self._process_expose(service)
235
relations, rel_svc_map = yield self._process_relation_map(
238
unit_matched = yield self._process_units(service,
242
# after filtering units check if any matched or remove the
243
# service from the output
244
if self.filter_units and not unit_matched:
245
del service_data[service.service_name]
248
yield self._process_relations(service, relations, rel_svc_map)
251
def _process_units(self, service, relations, rel_svc_map):
253
Gather unit information for a service::
256
agent-state: <started|pendding|etc>
257
machine: <machine id>
258
open-ports: ["port/protocol", ...]
259
public-address: <public dns name or ip>
261
<optional nested units of subordinate services>
264
`service`: ServiceState intance
265
`relations`: list of ServiceRelationState instance for this service
266
`rel_svc_map`: maps relation internal ids to the remote endpoint
267
service name. This references the name of the remote
268
endpoint and so is generated per service.
178
270
units = yield service.get_all_unit_states()
179
271
unit_matched = False
181
relations = yield relation_manager.get_relations_for_service(service)
183
273
for unit in units:
184
if len(filter_units):
274
if len(self.filter_units):
186
for filter_unit in filter_units:
276
for filter_unit in self.filter_units:
187
277
if fnmatch(unit.unit_name, filter_unit):
193
u = unit_data[unit.unit_name] = dict()
194
machine_id = yield unit.get_assigned_machine_id()
195
u["machine"] = machine_id
196
unit_workflow_client = WorkflowStateClient(client, unit)
197
unit_state = yield unit_workflow_client.get_state()
199
u["state"] = "pending"
201
unit_connected = yield unit.has_agent()
202
u["state"] = unit_state if unit_connected else "down"
204
open_ports = yield unit.get_open_ports()
205
u["open-ports"] = ["{port}/{proto}".format(**port_info)
206
for port_info in open_ports]
208
u["public-address"] = yield unit.get_public_address()
210
# indicate we should include information about this
212
seen_machines.add(machine_id)
282
yield self._process_unit(service, unit, relations, rel_svc_map)
213
283
unit_matched = True
215
# collect info on each relation for the service unit
217
for relation in relations:
219
relation_unit = yield relation.get_unit_state(unit)
220
except UnitRelationStateNotFound:
221
# This exception will occur when relations are
222
# established between services without service
223
# units, and therefore never have any
224
# corresponding service relation units. This
225
# scenario does not occur in actual deployments,
226
# but can happen in test circumstances. In
227
# particular, it will happen with a misconfigured
228
# provider, which exercises this codepath.
229
continue # should not occur, but status should not fail
230
relation_workflow_client = WorkflowStateClient(
231
client, relation_unit)
232
relation_workflow_state = \
233
yield relation_workflow_client.get_state()
234
relation_status[relation.relation_name] = dict(
235
state=relation_workflow_state)
236
u["relations"] = relation_status
238
# after filtering units check if any matched or remove the
239
# service from the output
240
if filter_units and not unit_matched:
241
del service_data[service.service_name]
244
for relation in relations:
245
rel_services = yield relation.get_service_states()
247
# A single related service implies a peer relation. More
248
# imply a bi-directional provides/requires relationship.
249
# In the later case we omit the local side of the relation
251
if len(rel_services) > 1:
252
# Filter out self from multi-service relations.
254
rsn for rsn in rel_services if rsn.service_name !=
255
service.service_name]
257
if len(rel_services) > 1:
258
raise ValueError("Unexpected relationship with more "
261
rel_service = rel_services[0]
262
relation_data[relation.relation_name] = rel_service.service_name
264
machines = yield machine_manager.get_all_machine_states()
265
for machine_state in machines:
266
if (filter_services or filter_units) and \
267
machine_state.id not in seen_machines:
284
returnValue(unit_matched)
287
def _process_unit(self, service, unit, relations, rel_svc_map):
288
""" Generate unit info for a single unit of a single service.
290
`unit`: ServiceUnitState
291
see `_process_units` for an explanation of other arguments.
294
u = self.unit_data[unit.unit_name] = dict()
295
container = yield unit.get_container()
298
u["container"] = container.unit_name
299
self.subordinates.setdefault(unit.service_name,
300
set()).add(container.service_name)
302
machine_id = yield unit.get_assigned_machine_id()
303
u["machine"] = machine_id
304
unit_workflow_client = WorkflowStateClient(self.client, unit)
305
unit_state = yield unit_workflow_client.get_state()
307
u["agent-state"] = "pending"
309
unit_connected = yield unit.has_agent()
310
u["agent-state"] = unit_state.replace("_", "-") \
311
if unit_connected else "down"
313
exposed = self.service_data[service.service_name].get("exposed")
315
open_ports = yield unit.get_open_ports()
316
u["open-ports"] = ["{port}/{proto}".format(**port_info)
317
for port_info in open_ports]
319
u["public-address"] = yield unit.get_public_address()
321
# indicate we should include information about this
323
self.seen_machines.add(machine_id)
325
# collect info on each relation for the service unit
326
yield self._process_unit_relations(service, unit,
327
relations, rel_svc_map)
330
def _process_relation_map(self, service):
331
"""Generate a mapping from a services relations to the service name of
332
the remote endpoints.
334
returns: ([ServiceRelationState, ...], mapping)
336
relation_data = self.service_data[service.service_name]["relations"]
337
relation_mgr = self.relation_manager
338
relations = yield relation_mgr.get_relations_for_service(service)
341
for relation in relations:
342
rel_services = yield relation.get_service_states()
344
# A single related service implies a peer relation. More
345
# imply a bi-directional provides/requires relationship.
346
# In the later case we omit the local side of the relation
348
if len(rel_services) > 1:
349
# Filter out self from multi-service relations.
351
rsn for rsn in rel_services if rsn.service_name !=
352
service.service_name]
354
if len(rel_services) > 1:
355
raise ValueError("Unexpected relationship with more "
358
rel_service = rel_services[0]
359
relation_data.setdefault(relation.relation_name, set()).add(
360
rel_service.service_name)
361
rel_svc_map[relation.internal_relation_id] = (
362
rel_service.service_name)
364
returnValue((relations, rel_svc_map))
367
def _process_relations(self, service, relations, rel_svc_map):
368
"""Generate relation information for a given service
370
Each service with relations will have a relations dict
371
nested under it with one or more relations described::
375
- <remote service name>
378
relation_data = self.service_data[service.service_name]["relations"]
380
for relation in relations:
381
rel_services = yield relation.get_service_states()
383
# A single related service implies a peer relation. More
384
# imply a bi-directional provides/requires relationship.
385
# In the later case we omit the local side of the relation
387
if len(rel_services) > 1:
388
# Filter out self from multi-service relations.
390
rsn for rsn in rel_services if rsn.service_name !=
391
service.service_name]
393
if len(rel_services) > 1:
394
raise ValueError("Unexpected relationship with more "
397
rel_service = rel_services[0]
398
relation_data.setdefault(
399
relation.relation_name, set()).add(
400
rel_service.service_name)
401
rel_svc_map[relation.internal_relation_id] = (
402
rel_service.service_name)
404
# Normalize the sets back to lists
405
for r in relation_data:
406
relation_data[r] = sorted(relation_data[r])
409
def _process_unit_relations(self, service, unit, relations, rel_svc_map):
410
"""Collect UnitRelationState information per relation and per unit.
412
Includes information under each unit for its relations including
413
its relation state and information about any possible errors.
415
see `_process_relations` for argument information
417
u = self.unit_data[unit.unit_name]
420
for relation in relations:
422
relation_unit = yield relation.get_unit_state(unit)
423
except UnitRelationStateNotFound:
424
# This exception will occur when relations are
425
# established between services without service
426
# units, and therefore never have any
427
# corresponding service relation units.
428
# UPDATE: common with subordinate services, and
429
# some testing scenarios.
431
relation_workflow_client = WorkflowStateClient(
432
self.client, relation_unit)
433
workflow_state = yield relation_workflow_client.get_state()
435
rel_svc_name = rel_svc_map.get(relation.internal_relation_id)
436
if rel_svc_name and workflow_state not in ("up", None):
437
relation_errors.setdefault(
438
relation.relation_name, set()).add(rel_svc_name)
441
# Normalize sets and store.
442
u["relation-errors"] = dict(
443
[(r, sorted(relation_errors[r])) for r in relation_errors])
445
def _process_subordinates(self):
446
"""Properly nest subordinate units under their principal service's
447
unit nodes. Services and units are generated in one pass, then
448
iterated by this method to structure the output data to reflect
449
actual unit containment.
451
Subordinate units will include the follow::
454
- <principal service names>
456
Principal services that have subordinates will include::
459
<subordinate unit name>:
460
agent-state: <agent state>
462
service_data = self.service_data
464
for unit_name, u in self.unit_data.iteritems():
465
container = u.get("container")
467
d = self.unit_data[container].setdefault("subordinates", {})
470
# remove keys that don't appear in output or come from container
471
for key in ("container", "machine", "public-address"):
474
service_name = parse_service_name(unit_name)
475
service_data[service_name]["units"][unit_name] = u
477
for sub_service, principal_services in self.subordinates.iteritems():
478
service_data[sub_service]["subordinate-to"] = sorted(principal_services)
479
service_data[sub_service].pop("units", None)
482
def _process_expose(self, service):
483
"""Indicate if a service is exposed or not."""
484
exposed = yield service.get_exposed_flag()
486
self.service_data[service.service_name].update(exposed=exposed)
490
def _process_machines(self):
491
"""Gather machine information.
495
agent-state: <agent state>
497
instance-id: <provider specific instance id>
498
instance-state: <instance state>
501
machines = yield self.machine_manager.get_all_machine_states()
502
for machine_state in machines:
503
if (self.filter_services or self.filter_units) and \
504
machine_state.id not in self.seen_machines:
506
yield self._process_machine(machine_state)
509
def _process_machine(self, machine_state):
511
`machine_state`: MachineState instance
270
513
instance_id = yield machine_state.get_instance_id()
271
514
m = {"instance-id": instance_id \
272
515
if instance_id is not None else "pending"}
273
516
if instance_id is not None:
275
pm = yield machine_provider.get_machine(instance_id)
518
pm = yield self.provider.get_machine(instance_id)
276
519
m["dns-name"] = pm.dns_name
277
520
m["instance-state"] = pm.state
278
521
if (yield machine_state.has_agent()):
279
522
# if the agent's connected, we're fine
280
m["state"] = "running"
523
m["agent-state"] = "running"
282
units = (yield machine_state.get_all_service_unit_states())
526
yield machine_state.get_all_service_unit_states())
283
527
for unit in units:
284
unit_workflow_client = WorkflowStateClient(client, unit)
528
unit_workflow_client = WorkflowStateClient(
285
530
if (yield unit_workflow_client.get_state()):
286
# for unit to have a state, its agent must have
287
# run, which implies the machine agent must have
288
# been running correctly at some point in the past
531
# for unit to have a state, its agent must
532
# have run, which implies the machine agent
533
# must have been running correctly at some
535
m["agent-state"] = "down"
292
538
# otherwise we're probably just still waiting
293
m["state"] = "not-started"
539
m["agent-state"] = "not-started"
294
540
except ProviderError:
295
541
# The provider doesn't have machine information
297
543
"Machine provider information missing: machine %s" % (
298
544
machine_state.id))
300
machine_data[machine_state.id] = m
546
self.machine_data[machine_state.id] = m
305
549
def render_yaml(data, filelike, environment):