blob: 59461342c37f0e374467e1a21c96573218f9e78c [file] [log] [blame]
J. Richard Barnettec542c432016-02-12 11:29:34 -08001# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""
J. Richard Barnette55228612016-02-16 17:38:06 -08006Framework for host verification and repair in Autotest.
J. Richard Barnettec542c432016-02-12 11:29:34 -08007
J. Richard Barnette55228612016-02-16 17:38:06 -08008The framework provides implementation code in support of `Host.verify()`
9and `Host.repair()` used in Verify and Repair special tasks.
J. Richard Barnettec542c432016-02-12 11:29:34 -080010
11The framework consists of these classes:
12 * `Verifier`: A class representing a single verification check.
J. Richard Barnette55228612016-02-16 17:38:06 -080013 * `RepairAction`: A class representing a repair operation that can fix
14 a failed verification check.
15 * `RepairStrategy`: A class for organizing a collection of `Verifier`
16 and `RepairAction` instances, and invoking them in order.
J. Richard Barnettec542c432016-02-12 11:29:34 -080017
J. Richard Barnette55228612016-02-16 17:38:06 -080018Individual operations during verification and repair are handled by
19instances of `Verifier` and `RepairAction`. `Verifier` objects are
20meant to test for specific conditions that may cause tests to fail.
21`RepairAction` objects provide operations designed to fix one or
22more failures identified by a `Verifier` object.
J. Richard Barnettec542c432016-02-12 11:29:34 -080023"""
24
Richard Barnettef6579932016-08-16 17:07:03 -070025import collections
J. Richard Barnettec542c432016-02-12 11:29:34 -080026import logging
27
28import common
29from autotest_lib.client.common_lib import error
30
31
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -070032class AutoservVerifyError(error.AutoservError):
J. Richard Barnettec542c432016-02-12 11:29:34 -080033 """
34 Generic Exception for failures from `Verifier` objects.
35
36 Instances of this exception can be raised when a `verify()`
37 method fails, if no more specific exception is available.
38 """
39 pass
40
41
Richard Barnettef6579932016-08-16 17:07:03 -070042_DependencyFailure = collections.namedtuple(
43 '_DependencyFailure', ('dependency', 'error'))
44
45
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -070046class AutoservVerifyDependencyError(error.AutoservError):
J. Richard Barnettec542c432016-02-12 11:29:34 -080047 """
48 Exception raised for failures in dependencies.
49
50 This exception is used to distinguish an original failure from a
51 failure being passed back from a verification dependency. That is,
52 if 'B' depends on 'A', and 'A' fails, 'B' will raise this exception
53 to signal that the original failure is further down the dependency
54 chain.
55
Richard Barnettef6579932016-08-16 17:07:03 -070056 The `failures` argument to the constructor for this class is a set
57 of instances of `_DependencyFailure`, each corresponding to one
58 failed dependency:
59 * The `dependency` attribute of each failure is the description
60 of the failed dependency.
61 * The `error` attribute of each failure is the string value of
62 the exception from the failed dependency.
J. Richard Barnettec542c432016-02-12 11:29:34 -080063
Richard Barnettef6579932016-08-16 17:07:03 -070064 Multiple methods in this module recognize and handle this exception
J. Richard Barnettec542c432016-02-12 11:29:34 -080065 specially.
Richard Barnettef6579932016-08-16 17:07:03 -070066
67 @property failures Set of failures passed to the constructor.
68 @property _node Instance of `_DependencyNode` reporting the
69 failed dependencies.
J. Richard Barnettec542c432016-02-12 11:29:34 -080070 """
Richard Barnettef6579932016-08-16 17:07:03 -070071 def __init__(self, node, failures):
72 """
73 Constructor for `AutoservVerifyDependencyError`.
74
75 @param node Instance of _DependencyNode reporting the
76 failed dependencies.
77 @param failures List of failure tuples as described above.
78 """
79 super(AutoservVerifyDependencyError, self).__init__(
80 '\n'.join([f.error for f in failures]))
81 self.failures = failures
82 self._node = node
83
84
85 def log_dependencies(self, action, deps):
86 """
87 Log an `AutoservVerifyDependencyError`.
88
89 This writes a short summary of the dependency failures captured
90 in this exception, using standard Python logging.
91
92 The passed in `action` string plus `self._node.description`
93 are logged at INFO level. The `action` argument should
94 introduce or describe an action relative to `self._node`.
95
96 The passed in `deps` string and the description of each failed
97 dependency in `self` are be logged at DEBUG level. The `deps`
98 argument is used to introduce the various failed dependencies.
99
100 @param action A string mentioning the action being logged
101 relative to `self._node`.
102 @param deps A string introducing the dependencies that
103 failed.
104 """
105 logging.info('%s: %s', action, self._node.description)
106 logging.debug('%s:', deps)
107 for failure in self.failures:
108 logging.debug(' %s', failure.dependency)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800109
110
J. Richard Barnette55228612016-02-16 17:38:06 -0800111class AutoservRepairError(error.AutoservError):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800112 """
J. Richard Barnette55228612016-02-16 17:38:06 -0800113 Generic Exception for failures from `RepairAction` objects.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800114
J. Richard Barnette55228612016-02-16 17:38:06 -0800115 Instances of this exception can be raised when a `repair()`
116 method fails, if no more specific exception is available.
117 """
118 pass
J. Richard Barnettec542c432016-02-12 11:29:34 -0800119
J. Richard Barnettec542c432016-02-12 11:29:34 -0800120
J. Richard Barnette55228612016-02-16 17:38:06 -0800121class _DependencyNode(object):
122 """
123 An object that can depend on verifiers.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800124
J. Richard Barnette55228612016-02-16 17:38:06 -0800125 Both repair and verify operations have the notion of dependencies
126 that must pass before the operation proceeds. This class captures
127 the shared behaviors required by both classes.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800128
129 @property tag Short identifier to be used in logging.
J. Richard Barnette55228612016-02-16 17:38:06 -0800130 @property description Text summary of this node's action, to be
131 used in debug logs.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800132 @property _dependency_list Dependency pre-requisites.
133 """
134
J. Richard Barnettea4784062016-03-04 14:14:10 -0800135 def __init__(self, tag, dependencies):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800136 self._dependency_list = dependencies
J. Richard Barnettea4784062016-03-04 14:14:10 -0800137 self._tag = tag
J. Richard Barnettec542c432016-02-12 11:29:34 -0800138
139
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700140 def _record(self, host, silent, *record_args):
141 """
142 Log a status record for `host`.
143
144 Call `host.record()` with the given `record_args`, unless
145 requested to skip by `silent`.
146
147 @param host Host which will record the status record.
148 @param silent Don't record the event if this is a true
149 value.
150 @param record_args Arguments to pass to `host.record()`.
151 """
152 if not silent:
153 host.record(*record_args)
154
155
156 def _verify_list(self, host, verifiers, silent):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800157 """
158 Test a list of verifiers against a given host.
159
160 This invokes `_verify_host()` on every verifier in the given
161 list. If any verifier in the transitive closure of dependencies
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700162 in the list fails, an `AutoservVerifyDependencyError` is raised
J. Richard Barnettec542c432016-02-12 11:29:34 -0800163 containing the description of each failed verifier. Only
164 original failures are reported; verifiers that don't run due
165 to a failed dependency are omitted.
166
167 By design, original failures are logged once in `_verify_host()`
168 when `verify()` originally fails. The additional data gathered
169 here is for the debug logs to indicate why a subsequent
170 operation never ran.
171
172 @param host The host to be tested against the verifiers.
173 @param verifiers List of verifiers to be checked.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700174 @param silent If true, don't log host status records.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800175
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700176 @raises AutoservVerifyDependencyError Raised when at least
J. Richard Barnettec542c432016-02-12 11:29:34 -0800177 one verifier in the list has failed.
178 """
J. Richard Barnette2654c552016-03-01 17:26:35 -0800179 failures = set()
J. Richard Barnettec542c432016-02-12 11:29:34 -0800180 for v in verifiers:
181 try:
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700182 v._verify_host(host, silent)
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700183 except AutoservVerifyDependencyError as e:
Richard Barnettef6579932016-08-16 17:07:03 -0700184 failures.update(e.failures)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800185 except Exception as e:
Richard Barnettef6579932016-08-16 17:07:03 -0700186 failures.add(_DependencyFailure(v.description, str(e)))
J. Richard Barnettec542c432016-02-12 11:29:34 -0800187 if failures:
Richard Barnettef6579932016-08-16 17:07:03 -0700188 raise AutoservVerifyDependencyError(self, failures)
J. Richard Barnette55228612016-02-16 17:38:06 -0800189
190
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700191 def _verify_dependencies(self, host, silent):
J. Richard Barnette55228612016-02-16 17:38:06 -0800192 """
193 Verify that all of this node's dependencies pass for a host.
194
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700195 @param host The host to be verified.
196 @param silent If true, don't log host status records.
J. Richard Barnette55228612016-02-16 17:38:06 -0800197 """
198 try:
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700199 self._verify_list(host, self._dependency_list, silent)
J. Richard Barnette55228612016-02-16 17:38:06 -0800200 except AutoservVerifyDependencyError as e:
Richard Barnettef6579932016-08-16 17:07:03 -0700201 e.log_dependencies(
202 'Skipping this operation',
203 'The following dependencies failed')
J. Richard Barnette55228612016-02-16 17:38:06 -0800204 raise
205
206
207 @property
208 def tag(self):
209 """
210 Tag for use in logging status records.
211
212 This is a property with a short string used to identify the node
213 in the 'status.log' file and during node construction. The tag
214 should contain only letters, digits, and '_' characters. This
215 tag is not used alone, but is combined with other identifiers,
216 based on the operation being logged.
217
218 @return A short identifier-like string.
219 """
220 return self._tag
221
222
223 @property
224 def description(self):
225 """
226 Text description of this node for log messages.
227
228 This string will be logged with failures, and should describe
229 the condition required for success.
230
231 N.B. Subclasses are required to override this method, but we
232 _don't_ raise NotImplementedError here. Various methods fail in
233 inscrutable ways if this method raises any exception, so for
234 debugging purposes, it's better to return a default value.
235
236 @return A descriptive string.
237 """
238 return ('Class %s fails to implement description().' %
239 type(self).__name__)
240
241
242class Verifier(_DependencyNode):
243 """
244 Abstract class embodying one verification check.
245
246 A concrete subclass of `Verifier` provides a simple check that can
247 determine a host's fitness for testing. Failure indicates that the
248 check found a problem that can cause at least one test to fail.
249
250 `Verifier` objects are organized in a DAG identifying dependencies
251 among operations. The DAG controls ordering and prevents wasted
252 effort: If verification operation V2 requires that verification
253 operation V1 pass, then a) V1 will run before V2, and b) if V1
254 fails, V2 won't run at all. The `_verify_host()` method ensures
255 that all dependencies run and pass before invoking the `verify()`
256 method.
257
258 A `Verifier` object caches its result the first time it calls
259 `verify()`. Subsequent calls return the cached result, without
260 re-running the check code. The `_reverify()` method clears the
261 cached result in the current node, and in all dependencies.
262
263 Subclasses must supply these properties and methods:
264 * `verify()`: This is the method to perform the actual
265 verification check.
266 * `description`: A one-line summary of the verification check for
267 debug log messages.
268
269 Subclasses must override all of the above attributes; subclasses
270 should not override or extend any other attributes of this class.
271
272 The description string should be a simple sentence explaining what
273 must be true for the verifier to pass. Do not include a terminating
274 period. For example:
275
276 Host is available via ssh
277
278 The base class manages the following private data:
279 * `_result`: The cached result of verification.
280 * `_dependency_list`: The list of dependencies.
281 Subclasses should not use these attributes.
282
283 @property _result Cached result of verification.
284 """
285
286 def __init__(self, tag, dependencies):
287 super(Verifier, self).__init__(tag, dependencies)
288 self._result = None
289 self._verify_tag = 'verify.' + self.tag
290
291
J. Richard Barnettec542c432016-02-12 11:29:34 -0800292 def _reverify(self):
293 """
294 Discard cached verification results.
295
296 Reset the cached verification result for this node, and for the
297 transitive closure of all dependencies.
298 """
299 if self._result is not None:
300 self._result = None
301 for v in self._dependency_list:
302 v._reverify()
303
304
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700305 def _verify_host(self, host, silent):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800306 """
307 Determine the result of verification, and log results.
308
309 If this verifier does not have a cached verification result,
310 check dependencies, and if they pass, run `verify()`. Log
311 informational messages regarding failed dependencies. If we
312 call `verify()`, log the result in `status.log`.
313
314 If we already have a cached result, return that result without
315 logging any message.
316
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700317 @param host The host to be tested for a problem.
318 @param silent If true, don't log host status records.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800319 """
320 if self._result is not None:
321 if isinstance(self._result, Exception):
322 raise self._result # cached failure
323 elif self._result:
324 return # cached success
325 self._result = False
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700326 self._verify_dependencies(host, silent)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800327 logging.info('Verifying this condition: %s', self.description)
328 try:
329 self.verify(host)
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700330 self._record(host, silent, 'GOOD', None, self._verify_tag)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800331 except Exception as e:
332 logging.exception('Failed: %s', self.description)
333 self._result = e
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700334 self._record(host, silent,
335 'FAIL', None, self._verify_tag, str(e))
J. Richard Barnettec542c432016-02-12 11:29:34 -0800336 raise
337 self._result = True
338
339
340 def verify(self, host):
341 """
342 Unconditionally perform a verification check.
343
344 This method is responsible for testing for a single problem on a
345 host. Implementations should follow these guidelines:
346 * The check should find a problem that will cause testing to
347 fail.
348 * Verification checks on a working system should run quickly
349 and should be optimized for success; a check that passes
350 should finish within seconds.
351 * Verification checks are not expected have side effects, but
352 may apply trivial fixes if they will finish within the time
353 constraints above.
354
355 A verification check should normally trigger a single set of
356 repair actions. If two different failures can require two
357 different repairs, ideally they should use two different
358 subclasses of `Verifier`.
359
360 Implementations indicate failure by raising an exception. The
361 exception text should be a short, 1-line summary of the error.
362 The text should be concise and diagnostic, as it will appear in
363 `status.log` files.
364
365 If this method finds no problems, it returns without raising any
366 exception.
367
368 Implementations should avoid most logging actions, but can log
369 DEBUG level messages if they provide significant information for
370 diagnosing failures.
371
372 @param host The host to be tested for a problem.
373 """
374 raise NotImplementedError('Class %s does not implement '
375 'verify()' % type(self).__name__)
376
377
J. Richard Barnette55228612016-02-16 17:38:06 -0800378class RepairAction(_DependencyNode):
379 """
380 Abstract class embodying one repair procedure.
381
382 A `RepairAction` is responsible for fixing one or more failed
383 `Verifier` checks, in order to make those checks pass.
384
385 Each repair action includes one or more verifier triggers that
386 determine when the repair action should run. A repair action
387 will call its `repair()` method if one or more of its triggers
388 fails. A repair action is successful if all of its triggers pass
389 after calling `repair()`.
390
391 A `RepairAction` is a subclass of `_DependencyNode`; if any of a
392 repair action's dependencies fail, the action does not check its
393 triggers, and doesn't call `repair()`.
394
395 Subclasses must supply these attributes:
396 * `repair()`: This is the method to perform the necessary
397 repair. The method should avoid most logging actions, but
398 can log DEBUG level messages if they provide significant
399 information for diagnosing failures.
400 * `description`: A one-line summary of the repair action for
401 debug log messages.
402
403 Subclasses must override both of the above attributes and should
404 not override any other attributes of this class.
405
406 The description string should be a simple sentence explaining the
407 operation that will be performed. Do not include a terminating
408 period. For example:
409
410 Re-install the stable build via AU
411
412 @property _trigger_list List of verification checks that will
413 trigger this repair when they fail.
414 """
415
416 def __init__(self, tag, dependencies, triggers):
417 super(RepairAction, self).__init__(tag, dependencies)
418 self._trigger_list = triggers
419 self._repair_tag = 'repair.' + self.tag
420
421
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700422 def _repair_host(self, host, silent):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800423 """
J. Richard Barnette55228612016-02-16 17:38:06 -0800424 Apply this repair action if any triggers fail.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800425
J. Richard Barnette55228612016-02-16 17:38:06 -0800426 Repair is triggered when all dependencies are successful, and at
427 least one trigger fails.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800428
J. Richard Barnette55228612016-02-16 17:38:06 -0800429 If the `repair()` method triggers, the success or failure of
430 this operation is logged in `status.log` bracketed by 'START'
431 and 'END' records. Details of whether or why `repair()`
432 triggered are written to the debug logs. If repair doesn't
433 trigger, nothing is logged to `status.log`.
434
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700435 @param host The host to be repaired.
436 @param silent If true, don't log host status records.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800437 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700438 self._verify_dependencies(host, silent)
J. Richard Barnette55228612016-02-16 17:38:06 -0800439 try:
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700440 self._verify_list(host, self._trigger_list, silent)
J. Richard Barnette55228612016-02-16 17:38:06 -0800441 except AutoservVerifyDependencyError as e:
Richard Barnettef6579932016-08-16 17:07:03 -0700442 e.log_dependencies(
443 'Attempting this repair action',
Richard Barnette121239d2016-08-25 14:22:05 -0700444 'Repairing because these triggers failed')
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700445 self._record(host, silent, 'START', None, self._repair_tag)
J. Richard Barnette55228612016-02-16 17:38:06 -0800446 try:
447 self.repair(host)
448 except Exception as e:
449 logging.exception('Repair failed: %s', self.description)
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700450 self._record(host, silent,
451 'FAIL', None, self._repair_tag, str(e))
452 self._record(host, silent,
453 'END FAIL', None, self._repair_tag)
J. Richard Barnette55228612016-02-16 17:38:06 -0800454 raise
455 try:
456 for v in self._trigger_list:
457 v._reverify()
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700458 self._verify_list(host, self._trigger_list, silent)
459 self._record(host, silent,
460 'END GOOD', None, self._repair_tag)
J. Richard Barnette55228612016-02-16 17:38:06 -0800461 except AutoservVerifyDependencyError as e:
Richard Barnettef6579932016-08-16 17:07:03 -0700462 e.log_dependencies(
463 'This repair action reported success',
464 'However, these triggers still fail')
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700465 self._record(host, silent,
466 'END FAIL', None, self._repair_tag)
J. Richard Barnette55228612016-02-16 17:38:06 -0800467 raise AutoservRepairError(
468 'Some verification checks still fail')
469 except Exception:
470 # The specification for `self._verify_list()` says
471 # that this can't happen; this is a defensive
472 # precaution.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700473 self._record(host, silent,
474 'END FAIL', None, self._repair_tag,
J. Richard Barnette55228612016-02-16 17:38:06 -0800475 'Internal error in repair')
476 raise
477 else:
478 logging.info('No failed triggers, skipping repair: %s',
479 self.description)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800480
481
J. Richard Barnette55228612016-02-16 17:38:06 -0800482 def repair(self, host):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800483 """
J. Richard Barnette55228612016-02-16 17:38:06 -0800484 Apply this repair action to the given host.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800485
J. Richard Barnette55228612016-02-16 17:38:06 -0800486 This method is responsible for applying changes to fix failures
487 in one or more verification checks. The repair is considered
488 successful if the DUT passes the specific checks after this
489 method completes.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800490
J. Richard Barnette55228612016-02-16 17:38:06 -0800491 Implementations indicate failure by raising an exception. The
492 exception text should be a short, 1-line summary of the error.
493 The text should be concise and diagnostic, as it will appear in
494 `status.log` files.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800495
J. Richard Barnette55228612016-02-16 17:38:06 -0800496 If this method completes successfully, it returns without
497 raising any exception.
498
499 Implementations should avoid most logging actions, but can log
500 DEBUG level messages if they provide significant information for
501 diagnosing failures.
502
503 @param host The host to be repaired.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800504 """
J. Richard Barnette55228612016-02-16 17:38:06 -0800505 raise NotImplementedError('Class %s does not implement '
506 'repair()' % type(self).__name__)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800507
508
509class _RootVerifier(Verifier):
510 """
511 Utility class used by `RepairStrategy`.
512
513 A node of this class by itself does nothing; it always passes (if it
514 can run). This class exists merely to be the root of a DAG of
515 dependencies in an instance of `RepairStrategy`.
516 """
517
J. Richard Barnettec542c432016-02-12 11:29:34 -0800518 def verify(self, host):
519 pass
520
521
522 @property
J. Richard Barnettec542c432016-02-12 11:29:34 -0800523 def description(self):
524 return 'All host verification checks pass'
525
526
527
528class RepairStrategy(object):
529 """
J. Richard Barnette55228612016-02-16 17:38:06 -0800530 A class for organizing `Verifier` and `RepairAction` objects.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800531
J. Richard Barnette2654c552016-03-01 17:26:35 -0800532 An instance of `RepairStrategy` is organized as a DAG of `Verifier`
J. Richard Barnette55228612016-02-16 17:38:06 -0800533 objects, plus a list of `RepairAction` objects. The class provides
534 methods for invoking those objects in the required order, when
535 needed:
536 * The `verify()` method walks the verifier DAG in dependency
537 order.
538 * The `repair()` method invokes the repair actions in list order.
539 Each repair action will invoke its dependencies and triggers as
540 needed.
J. Richard Barnette2654c552016-03-01 17:26:35 -0800541
J. Richard Barnette55228612016-02-16 17:38:06 -0800542 # The Verifier DAG
543 The verifier DAG is constructed from the first argument passed to
544 the passed to the `RepairStrategy` constructor. That argument is an
545 iterable consisting of three-element tuples in the form
546 `(constructor, tag, deps)`:
J. Richard Barnette2654c552016-03-01 17:26:35 -0800547 * The `constructor` value is a callable that creates a `Verifier`
J. Richard Barnette55228612016-02-16 17:38:06 -0800548 as for the interface of the class constructor. For classes
J. Richard Barnette2654c552016-03-01 17:26:35 -0800549 that inherit the default constructor from `Verifier`, this can
550 be the class itself.
J. Richard Barnettea4784062016-03-04 14:14:10 -0800551 * The `tag` value is the tag to be associated with the constructed
552 verifier.
J. Richard Barnette2654c552016-03-01 17:26:35 -0800553 * The `deps` value is an iterable (e.g. list or tuple) of strings.
554 Each string corresponds to the `tag` member of a `Verifier`
555 dependency.
556
557 The tag names of verifiers in the constructed DAG must all be
J. Richard Barnette55228612016-02-16 17:38:06 -0800558 unique. The tag name defined by `RepairStrategy.ROOT_TAG` is
559 reserved and may not be used by any verifier.
J. Richard Barnette2654c552016-03-01 17:26:35 -0800560
561 In the input data for the constructor, dependencies must appear
J. Richard Barnettea4784062016-03-04 14:14:10 -0800562 before the nodes that depend on them. Thus:
J. Richard Barnette2654c552016-03-01 17:26:35 -0800563
J. Richard Barnettea4784062016-03-04 14:14:10 -0800564 ((A, 'a', ()), (B, 'b', ('a',))) # This is valid
565 ((B, 'b', ('a',)), (A, 'a', ())) # This will fail!
J. Richard Barnette2654c552016-03-01 17:26:35 -0800566
567 Internally, the DAG of verifiers is given unique root node. So,
568 given this input:
569
J. Richard Barnettea4784062016-03-04 14:14:10 -0800570 ((C, 'c', ()),
571 (A, 'a', ('c',)),
572 (B, 'b', ('c',)))
J. Richard Barnette2654c552016-03-01 17:26:35 -0800573
574 The following DAG is constructed:
575
576 Root
577 / \
578 A B
579 \ /
580 C
581
582 Since nothing depends on `A` or `B`, the root node guarantees that
583 these two verifiers will both be called and properly logged.
584
J. Richard Barnette55228612016-02-16 17:38:06 -0800585 The root node is not directly accessible; however repair actions can
586 trigger on it by using `RepairStrategy.ROOT_TAG`. Additionally, the
587 node will be logged in `status.log` whenever `verify()` succeeds.
588
589 # The Repair Actions List
590 The list of repair actions is constructed from the second argument
591 passed to the passed to the `RepairStrategy` constructor. That
592 argument is an iterable consisting of four-element tuples in the
593 form `(constructor, tag, deps, triggers)`:
594 * The `constructor` value is a callable that creates a
595 `RepairAction` as for the interface of the class constructor.
596 For classes that inherit the default constructor from
597 `RepairAction`, this can be the class itself.
598 * The `tag` value is the tag to be associated with the constructed
599 repair action.
600 * The `deps` value is an iterable (e.g. list or tuple) of strings.
601 Each string corresponds to the `tag` member of a `Verifier` that
602 the repair action depends on.
603 * The `triggers` value is an iterable (e.g. list or tuple) of
604 strings. Each string corresponds to the `tag` member of a
605 `Verifier` that can trigger the repair action.
606
607 `RepairStrategy` deps and triggers can only refer to verifiers,
608 not to other repair actions.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800609 """
610
J. Richard Barnettea4784062016-03-04 14:14:10 -0800611 # This name is reserved; clients may not use it.
J. Richard Barnette55228612016-02-16 17:38:06 -0800612 ROOT_TAG = 'PASS'
J. Richard Barnette2654c552016-03-01 17:26:35 -0800613
J. Richard Barnette55228612016-02-16 17:38:06 -0800614 @staticmethod
615 def _add_verifier(verifiers, constructor, tag, dep_tags):
616 """
617 Construct and remember a verifier.
618
619 Create a `Verifier` using `constructor` and `tag`. Dependencies
620 for construction are found by looking up `dep_tags` in the
621 `verifiers` dictionary.
622
623 After construction, the new verifier is added to `verifiers`.
624
625 @param verifiers Dictionary of verifiers, indexed by tag.
626 @param constructor Verifier construction function.
627 @param tag Tag parameter for the construction function.
628 @param dep_tags Tags of dependencies for the constructor, to
629 be found in `verifiers`.
630 """
631 assert tag not in verifiers
632 deps = [verifiers[d] for d in dep_tags]
633 verifiers[tag] = constructor(tag, deps)
634
635
636 def __init__(self, verifier_data, repair_data):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800637 """
638 Construct a `RepairStrategy` from simplified DAG data.
639
640 The input `verifier_data` object describes how to construct
641 verify nodes and the dependencies that relate them, as detailed
J. Richard Barnette2654c552016-03-01 17:26:35 -0800642 above.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800643
J. Richard Barnette55228612016-02-16 17:38:06 -0800644 The input `repair_data` object describes how to construct repair
645 actions and their dependencies and triggers, as detailed above.
646
J. Richard Barnettec542c432016-02-12 11:29:34 -0800647 @param verifier_data Iterable value with constructors for the
648 elements of the verification DAG and their
649 dependencies.
J. Richard Barnette55228612016-02-16 17:38:06 -0800650 @param repair_data Iterable value with constructors for the
651 elements of the repair action list, and
652 their dependencies and triggers.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800653 """
J. Richard Barnette2654c552016-03-01 17:26:35 -0800654 # We use the `all_verifiers` list to guarantee that our root
655 # verifier will execute its dependencies in the order provided
656 # to us by our caller.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800657 verifier_map = {}
J. Richard Barnette55228612016-02-16 17:38:06 -0800658 all_tags = []
J. Richard Barnette2654c552016-03-01 17:26:35 -0800659 dependencies = set()
J. Richard Barnette55228612016-02-16 17:38:06 -0800660 for constructor, tag, deps in verifier_data:
661 self._add_verifier(verifier_map, constructor, tag, deps)
J. Richard Barnette2654c552016-03-01 17:26:35 -0800662 dependencies.update(deps)
J. Richard Barnette55228612016-02-16 17:38:06 -0800663 all_tags.append(tag)
J. Richard Barnette2654c552016-03-01 17:26:35 -0800664 # Capture all the verifiers that have nothing depending on them.
J. Richard Barnette55228612016-02-16 17:38:06 -0800665 root_tags = [t for t in all_tags if t not in dependencies]
666 self._add_verifier(verifier_map, _RootVerifier,
667 self.ROOT_TAG, root_tags)
668 self._verify_root = verifier_map[self.ROOT_TAG]
669 self._repair_actions = []
670 for constructor, tag, deps, triggers in repair_data:
671 r = constructor(tag,
672 [verifier_map[d] for d in deps],
673 [verifier_map[t] for t in triggers])
674 self._repair_actions.append(r)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800675
676
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700677 def verify(self, host, silent=False):
J. Richard Barnettec542c432016-02-12 11:29:34 -0800678 """
679 Run the verifier DAG on the given host.
680
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700681 @param host The target to be verified.
682 @param silent If true, don't log host status records.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800683 """
684 self._verify_root._reverify()
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700685 self._verify_root._verify_host(host, silent)
J. Richard Barnette55228612016-02-16 17:38:06 -0800686
687
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700688 def repair(self, host, silent=False):
J. Richard Barnette55228612016-02-16 17:38:06 -0800689 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700690 Run the repair list on the given host.
J. Richard Barnette55228612016-02-16 17:38:06 -0800691
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700692 @param host The target to be repaired.
693 @param silent If true, don't log host status records.
J. Richard Barnette55228612016-02-16 17:38:06 -0800694 """
695 self._verify_root._reverify()
696 for ra in self._repair_actions:
697 try:
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700698 ra._repair_host(host, silent)
J. Richard Barnette55228612016-02-16 17:38:06 -0800699 except Exception as e:
700 # all logging and exception handling was done at
701 # lower levels
702 pass
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700703 self._verify_root._verify_host(host, silent)