Home | History | Annotate | Download | only in hosts
      1 # 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 """
      6 Framework for host verification and repair in Autotest.
      7 
      8 The framework provides implementation code in support of `Host.verify()`
      9 and `Host.repair()` used in Verify and Repair special tasks.
     10 
     11 The framework consists of these classes:
     12   * `Verifier`: A class representing a single verification check.
     13   * `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.
     17 
     18 Individual operations during verification and repair are handled by
     19 instances of `Verifier` and `RepairAction`.  `Verifier` objects are
     20 meant to test for specific conditions that may cause tests to fail.
     21 `RepairAction` objects provide operations designed to fix one or
     22 more failures identified by a `Verifier` object.
     23 """
     24 
     25 import collections
     26 import logging
     27 
     28 import common
     29 from autotest_lib.client.common_lib import error
     30 
     31 try:
     32     from chromite.lib import metrics
     33 except ImportError:
     34     from autotest_lib.client.bin.utils import metrics_mock as metrics
     35 
     36 
     37 class AutoservVerifyError(error.AutoservError):
     38     """
     39     Generic Exception for failures from `Verifier` objects.
     40 
     41     Instances of this exception can be raised when a `verify()`
     42     method fails, if no more specific exception is available.
     43     """
     44     pass
     45 
     46 
     47 _DependencyFailure = collections.namedtuple(
     48         '_DependencyFailure', ('dependency', 'error'))
     49 
     50 
     51 class AutoservVerifyDependencyError(error.AutoservError):
     52     """
     53     Exception raised for failures in dependencies.
     54 
     55     This exception is used to distinguish an original failure from a
     56     failure being passed back from a verification dependency.  That is,
     57     if 'B' depends on 'A', and 'A' fails, 'B' will raise this exception
     58     to signal that the original failure is further down the dependency
     59     chain.
     60 
     61     The `failures` argument to the constructor for this class is a set
     62     of instances of `_DependencyFailure`, each corresponding to one
     63     failed dependency:
     64       * The `dependency` attribute of each failure is the description
     65         of the failed dependency.
     66       * The `error` attribute of each failure is the string value of
     67         the exception from the failed dependency.
     68 
     69     Multiple methods in this module recognize and handle this exception
     70     specially.
     71 
     72     @property failures  Set of failures passed to the constructor.
     73     @property _node     Instance of `_DependencyNode` reporting the
     74                         failed dependencies.
     75     """
     76 
     77     def __init__(self, node, failures):
     78         """
     79         Constructor for `AutoservVerifyDependencyError`.
     80 
     81         @param node       Instance of _DependencyNode reporting the
     82                           failed dependencies.
     83         @param failures   List of failure tuples as described above.
     84         """
     85         super(AutoservVerifyDependencyError, self).__init__(
     86                 '\n'.join([f.error for f in failures]))
     87         self.failures = failures
     88         self._node = node
     89 
     90     def log_dependencies(self, action, deps):
     91         """
     92         Log an `AutoservVerifyDependencyError`.
     93 
     94         This writes a short summary of the dependency failures captured
     95         in this exception, using standard Python logging.
     96 
     97         The passed in `action` string plus `self._node.description`
     98         are logged at INFO level.  The `action` argument should
     99         introduce or describe an action relative to `self._node`.
    100 
    101         The passed in `deps` string and the description of each failed
    102         dependency in `self` are be logged at DEBUG level.  The `deps`
    103         argument is used to introduce the various failed dependencies.
    104 
    105         @param action   A string mentioning the action being logged
    106                         relative to `self._node`.
    107         @param deps     A string introducing the dependencies that
    108                         failed.
    109         """
    110         logging.info('%s: %s', action, self._node.description)
    111         logging.debug('%s:', deps)
    112         for failure in self.failures:
    113             logging.debug('    %s', failure.dependency)
    114 
    115 
    116 class AutoservRepairError(error.AutoservError):
    117     """
    118     Generic Exception for failures from `RepairAction` objects.
    119 
    120     Instances of this exception can be raised when a `repair()`
    121     method fails, if no more specific exception is available.
    122     """
    123     pass
    124 
    125 
    126 class _DependencyNode(object):
    127     """
    128     An object that can depend on verifiers.
    129 
    130     Both repair and verify operations have the notion of dependencies
    131     that must pass before the operation proceeds.  This class captures
    132     the shared behaviors required by both classes.
    133 
    134     @property tag               Short identifier to be used in logging.
    135     @property description       Text summary of this node's action, to be
    136                                 used in debug logs.
    137     @property _dependency_list  Dependency pre-requisites.
    138     """
    139 
    140     def __init__(self, tag, record_type, dependencies):
    141         self._dependency_list = dependencies
    142         self._tag = tag
    143         self._record_tag = record_type + '.' + tag
    144 
    145     def _record(self, host, silent, status_code, *record_args):
    146         """
    147         Log a status record for `host`.
    148 
    149         Call `host.record()` using the given status_code, and
    150         operation tag `self._record_tag`, plus any extra arguments in
    151         `record_args`.  Do nothing if `silent` is a true value.
    152 
    153         @param host         Host which will record the status record.
    154         @param silent       Don't record the event if this is a true
    155                             value.
    156         @param status_code  Value for the `status_code` parameter to
    157                             `host.record()`.
    158         @param record_args  Additional arguments to pass to
    159                             `host.record()`.
    160         """
    161         if not silent:
    162             host.record(status_code, None, self._record_tag,
    163                         *record_args)
    164 
    165     def _record_good(self, host, silent):
    166         """Log a 'GOOD' status line.
    167 
    168         @param host         Host which will record the status record.
    169         @param silent       Don't record the event if this is a true
    170                             value.
    171         """
    172         self._record(host, silent, 'GOOD')
    173 
    174     def _record_fail(self, host, silent, exc):
    175         """Log a 'FAIL' status line.
    176 
    177         @param host         Host which will record the status record.
    178         @param silent       Don't record the event if this is a true
    179                             value.
    180         @param exc          Exception describing the cause of failure.
    181         """
    182         self._record(host, silent, 'FAIL', str(exc))
    183 
    184     def _verify_list(self, host, verifiers, silent):
    185         """
    186         Test a list of verifiers against a given host.
    187 
    188         This invokes `_verify_host()` on every verifier in the given
    189         list.  If any verifier in the transitive closure of dependencies
    190         in the list fails, an `AutoservVerifyDependencyError` is raised
    191         containing the description of each failed verifier.  Only
    192         original failures are reported; verifiers that don't run due
    193         to a failed dependency are omitted.
    194 
    195         By design, original failures are logged once in `_verify_host()`
    196         when `verify()` originally fails.  The additional data gathered
    197         here is for the debug logs to indicate why a subsequent
    198         operation never ran.
    199 
    200         @param host       The host to be tested against the verifiers.
    201         @param verifiers  List of verifiers to be checked.
    202         @param silent     If true, don't log host status records.
    203 
    204         @raises AutoservVerifyDependencyError   Raised when at least
    205                         one verifier in the list has failed.
    206         """
    207         failures = set()
    208         for v in verifiers:
    209             try:
    210                 v._verify_host(host, silent)
    211             except AutoservVerifyDependencyError as e:
    212                 failures.update(e.failures)
    213             except Exception as e:
    214                 failures.add(_DependencyFailure(v.description, str(e)))
    215         if failures:
    216             raise AutoservVerifyDependencyError(self, failures)
    217 
    218     def _verify_dependencies(self, host, silent):
    219         """
    220         Verify that all of this node's dependencies pass for a host.
    221 
    222         @param host     The host to be verified.
    223         @param silent   If true, don't log host status records.
    224         """
    225         try:
    226             self._verify_list(host, self._dependency_list, silent)
    227         except AutoservVerifyDependencyError as e:
    228             e.log_dependencies(
    229                     'Skipping this operation',
    230                     'The following dependencies failed')
    231             raise
    232 
    233     @property
    234     def tag(self):
    235         """
    236         Tag for use in logging status records.
    237 
    238         This is a property with a short string used to identify the node
    239         in the 'status.log' file and during node construction.  The tag
    240         should contain only letters, digits, and '_' characters.  This
    241         tag is not used alone, but is combined with other identifiers,
    242         based on the operation being logged.
    243 
    244         @return A short identifier-like string.
    245         """
    246         return self._tag
    247 
    248     @property
    249     def description(self):
    250         """
    251         Text description of this node for log messages.
    252 
    253         This string will be logged with failures, and should describe
    254         the condition required for success.
    255 
    256         N.B. Subclasses are required to override this method, but we
    257         _don't_ raise NotImplementedError here.  Various methods fail in
    258         inscrutable ways if this method raises any exception, so for
    259         debugging purposes, it's better to return a default value.
    260 
    261         @return A descriptive string.
    262         """
    263         return ('Class %s fails to implement description().' %
    264                 type(self).__name__)
    265 
    266 
    267 class Verifier(_DependencyNode):
    268     """
    269     Abstract class embodying one verification check.
    270 
    271     A concrete subclass of `Verifier` provides a simple check that can
    272     determine a host's fitness for testing.  Failure indicates that the
    273     check found a problem that can cause at least one test to fail.
    274 
    275     `Verifier` objects are organized in a DAG identifying dependencies
    276     among operations.  The DAG controls ordering and prevents wasted
    277     effort:  If verification operation V2 requires that verification
    278     operation V1 pass, then a) V1 will run before V2, and b) if V1
    279     fails, V2 won't run at all.  The `_verify_host()` method ensures
    280     that all dependencies run and pass before invoking the `verify()`
    281     method.
    282 
    283     A `Verifier` object caches its result the first time it calls
    284     `verify()`.  Subsequent calls return the cached result, without
    285     re-running the check code.  The `_reverify()` method clears the
    286     cached result in the current node, and in all dependencies.
    287 
    288     Subclasses must supply these properties and methods:
    289       * `verify()`: This is the method to perform the actual
    290         verification check.
    291       * `description`:  A one-line summary of the verification check for
    292         debug log messages.
    293 
    294     Subclasses must override all of the above attributes; subclasses
    295     should not override or extend any other attributes of this class.
    296 
    297     The description string should be a simple sentence explaining what
    298     must be true for the verifier to pass.  Do not include a terminating
    299     period.  For example:
    300 
    301         Host is available via ssh
    302 
    303     The base class manages the following private data:
    304       * `_result`:  The cached result of verification.
    305       * `_dependency_list`:  The list of dependencies.
    306     Subclasses should not use these attributes.
    307 
    308     @property _result           Cached result of verification.
    309     """
    310 
    311     def __init__(self, tag, dependencies):
    312         super(Verifier, self).__init__(tag, 'verify', dependencies)
    313         self._result = None
    314 
    315     def _reverify(self):
    316         """
    317         Discard cached verification results.
    318 
    319         Reset the cached verification result for this node, and for the
    320         transitive closure of all dependencies.
    321         """
    322         if self._result is not None:
    323             self._result = None
    324             for v in self._dependency_list:
    325                 v._reverify()
    326 
    327     def _verify_host(self, host, silent):
    328         """
    329         Determine the result of verification, and log results.
    330 
    331         If this verifier does not have a cached verification result,
    332         check dependencies, and if they pass, run `verify()`.  Log
    333         informational messages regarding failed dependencies.  If we
    334         call `verify()`, log the result in `status.log`.
    335 
    336         If we already have a cached result, return that result without
    337         logging any message.
    338 
    339         @param host     The host to be tested for a problem.
    340         @param silent   If true, don't log host status records.
    341         """
    342         if self._result is not None:
    343             if isinstance(self._result, Exception):
    344                 raise self._result  # cached failure
    345             elif self._result:
    346                 return              # cached success
    347         self._result = False
    348         self._verify_dependencies(host, silent)
    349         logging.info('Verifying this condition: %s', self.description)
    350         try:
    351             self.verify(host)
    352             self._record_good(host, silent)
    353         except Exception as e:
    354             logging.exception('Failed: %s', self.description)
    355             self._result = e
    356             self._record_fail(host, silent, e)
    357             raise
    358         self._result = True
    359 
    360     def verify(self, host):
    361         """
    362         Unconditionally perform a verification check.
    363 
    364         This method is responsible for testing for a single problem on a
    365         host.  Implementations should follow these guidelines:
    366           * The check should find a problem that will cause testing to
    367             fail.
    368           * Verification checks on a working system should run quickly
    369             and should be optimized for success; a check that passes
    370             should finish within seconds.
    371           * Verification checks are not expected have side effects, but
    372             may apply trivial fixes if they will finish within the time
    373             constraints above.
    374 
    375         A verification check should normally trigger a single set of
    376         repair actions.  If two different failures can require two
    377         different repairs, ideally they should use two different
    378         subclasses of `Verifier`.
    379 
    380         Implementations indicate failure by raising an exception.  The
    381         exception text should be a short, 1-line summary of the error.
    382         The text should be concise and diagnostic, as it will appear in
    383         `status.log` files.
    384 
    385         If this method finds no problems, it returns without raising any
    386         exception.
    387 
    388         Implementations should avoid most logging actions, but can log
    389         DEBUG level messages if they provide significant information for
    390         diagnosing failures.
    391 
    392         @param host   The host to be tested for a problem.
    393         """
    394         raise NotImplementedError('Class %s does not implement '
    395                                   'verify()' % type(self).__name__)
    396 
    397 
    398 class RepairAction(_DependencyNode):
    399     """
    400     Abstract class embodying one repair procedure.
    401 
    402     A `RepairAction` is responsible for fixing one or more failed
    403     `Verifier` checks, in order to make those checks pass.
    404 
    405     Each repair action includes one or more verifier triggers that
    406     determine when the repair action should run.  A repair action
    407     will call its `repair()` method if one or more of its triggers
    408     fails.  A repair action is successful if all of its triggers pass
    409     after calling `repair()`.
    410 
    411     A `RepairAction` is a subclass of `_DependencyNode`; if any of a
    412     repair action's dependencies fail, the action does not check its
    413     triggers, and doesn't call `repair()`.
    414 
    415     Subclasses must supply these attributes:
    416       * `repair()`: This is the method to perform the necessary
    417         repair.  The method should avoid most logging actions, but
    418         can log DEBUG level messages if they provide significant
    419         information for diagnosing failures.
    420       * `description`:  A one-line summary of the repair action for
    421         debug log messages.
    422 
    423     Subclasses must override both of the above attributes and should
    424     not override any other attributes of this class.
    425 
    426     The description string should be a simple sentence explaining the
    427     operation that will be performed.  Do not include a terminating
    428     period.  For example:
    429 
    430         Re-install the stable build via AU
    431 
    432     @property _trigger_list   List of verification checks that will
    433                               trigger this repair when they fail.
    434     """
    435 
    436     def __init__(self, tag, dependencies, triggers):
    437         super(RepairAction, self).__init__(tag, 'repair', dependencies)
    438         self._trigger_list = triggers
    439 
    440     def _record_start(self, host, silent):
    441         """Log a 'START' status line.
    442 
    443         @param host         Host which will record the status record.
    444         @param silent       Don't record the event if this is a true
    445                             value.
    446         """
    447         self._record(host, silent, 'START')
    448 
    449     def _record_end_good(self, host, silent):
    450         """Log an 'END GOOD' status line.
    451 
    452         @param host         Host which will record the status record.
    453         @param silent       Don't record the event if this is a true
    454                             value.
    455         """
    456         self._record(host, silent, 'END GOOD')
    457         self.status = 'repaired'
    458 
    459     def _record_end_fail(self, host, silent, status, *args):
    460         """Log an 'END FAIL' status line.
    461 
    462         @param host         Host which will record the status record.
    463         @param silent       Don't record the event if this is a true
    464                             value.
    465         @param args         Extra arguments to `self._record()`
    466         """
    467         self._record(host, silent, 'END FAIL', *args)
    468         self.status = status
    469 
    470     def _repair_host(self, host, silent):
    471         """
    472         Apply this repair action if any triggers fail.
    473 
    474         Repair is triggered when all dependencies are successful, and at
    475         least one trigger fails.
    476 
    477         If the `repair()` method triggers, the success or failure of
    478         this operation is logged in `status.log` bracketed by 'START'
    479         and 'END' records.  Details of whether or why `repair()`
    480         triggered are written to the debug logs.   If repair doesn't
    481         trigger, nothing is logged to `status.log`.
    482 
    483         @param host     The host to be repaired.
    484         @param silent   If true, don't log host status records.
    485         """
    486         # Note:  Every exit path from the method must set `self.status`.
    487         # There's a lot of exit paths, so be careful.
    488         #
    489         # If we're blocked by a failed dependency, we exit with an
    490         # exception.  So set status to 'blocked' first.
    491         self.status = 'blocked'
    492         self._verify_dependencies(host, silent)
    493         # This is a defensive action.  Every path below should overwrite
    494         # this setting, but if it doesn't, we want our status to reflect
    495         # a coding error.
    496         self.status = 'unknown'
    497         try:
    498             self._verify_list(host, self._trigger_list, silent)
    499         except AutoservVerifyDependencyError as e:
    500             e.log_dependencies(
    501                     'Attempting this repair action',
    502                     'Repairing because these triggers failed')
    503             self._record_start(host, silent)
    504             try:
    505                 self.repair(host)
    506             except Exception as e:
    507                 logging.exception('Repair failed: %s', self.description)
    508                 self._record_fail(host, silent, e)
    509                 self._record_end_fail(host, silent, 'failed-action')
    510                 raise
    511             try:
    512                 for v in self._trigger_list:
    513                     v._reverify()
    514                 self._verify_list(host, self._trigger_list, silent)
    515                 self._record_end_good(host, silent)
    516             except AutoservVerifyDependencyError as e:
    517                 e.log_dependencies(
    518                         'This repair action reported success',
    519                         'However, these triggers still fail')
    520                 self._record_end_fail(host, silent, 'failed-trigger')
    521                 raise AutoservRepairError(
    522                         'Some verification checks still fail')
    523             except Exception:
    524                 # The specification for `self._verify_list()` says
    525                 # that this can't happen; this is a defensive
    526                 # precaution.
    527                 self._record_end_fail(host, silent, 'unknown',
    528                                       'Internal error in repair')
    529                 raise
    530         else:
    531             self.status = 'untriggered'
    532             logging.info('No failed triggers, skipping repair:  %s',
    533                          self.description)
    534 
    535     def repair(self, host):
    536         """
    537         Apply this repair action to the given host.
    538 
    539         This method is responsible for applying changes to fix failures
    540         in one or more verification checks.  The repair is considered
    541         successful if the DUT passes the specific checks after this
    542         method completes.
    543 
    544         Implementations indicate failure by raising an exception.  The
    545         exception text should be a short, 1-line summary of the error.
    546         The text should be concise and diagnostic, as it will appear in
    547         `status.log` files.
    548 
    549         If this method completes successfully, it returns without
    550         raising any exception.
    551 
    552         Implementations should avoid most logging actions, but can log
    553         DEBUG level messages if they provide significant information for
    554         diagnosing failures.
    555 
    556         @param host   The host to be repaired.
    557         """
    558         raise NotImplementedError('Class %s does not implement '
    559                                   'repair()' % type(self).__name__)
    560 
    561 
    562 class _RootVerifier(Verifier):
    563     """
    564     Utility class used by `RepairStrategy`.
    565 
    566     A node of this class by itself does nothing; it always passes (if it
    567     can run).  This class exists merely to be the root of a DAG of
    568     dependencies in an instance of `RepairStrategy`.
    569     """
    570 
    571     def verify(self, host):
    572         pass
    573 
    574     @property
    575     def description(self):
    576         return 'All host verification checks pass'
    577 
    578 
    579 
    580 class RepairStrategy(object):
    581     """
    582     A class for organizing `Verifier` and `RepairAction` objects.
    583 
    584     An instance of `RepairStrategy` is organized as a DAG of `Verifier`
    585     objects, plus a list of `RepairAction` objects.  The class provides
    586     methods for invoking those objects in the required order, when
    587     needed:
    588       * The `verify()` method walks the verifier DAG in dependency
    589         order.
    590       * The `repair()` method invokes the repair actions in list order.
    591         Each repair action will invoke its dependencies and triggers as
    592         needed.
    593 
    594     # The Verifier DAG
    595     The verifier DAG is constructed from the first argument passed to
    596     the passed to the `RepairStrategy` constructor.  That argument is an
    597     iterable consisting of three-element tuples in the form
    598     `(constructor, tag, deps)`:
    599       * The `constructor` value is a callable that creates a `Verifier`
    600         as for the interface of the class constructor.  For classes
    601         that inherit the default constructor from `Verifier`, this can
    602         be the class itself.
    603       * The `tag` value is the tag to be associated with the constructed
    604         verifier.
    605       * The `deps` value is an iterable (e.g. list or tuple) of strings.
    606         Each string corresponds to the `tag` member of a `Verifier`
    607         dependency.
    608 
    609     The tag names of verifiers in the constructed DAG must all be
    610     unique.  The tag name defined by `RepairStrategy.ROOT_TAG` is
    611     reserved and may not be used by any verifier.
    612 
    613     In the input data for the constructor, dependencies must appear
    614     before the nodes that depend on them.  Thus:
    615 
    616         ((A, 'a', ()), (B, 'b', ('a',)))     # This is valid
    617         ((B, 'b', ('a',)), (A, 'a', ()))     # This will fail!
    618 
    619     Internally, the DAG of verifiers is given unique root node.  So,
    620     given this input:
    621 
    622         ((C, 'c', ()),
    623          (A, 'a', ('c',)),
    624          (B, 'b', ('c',)))
    625 
    626     The following DAG is constructed:
    627 
    628           Root
    629           /  \
    630          A    B
    631           \  /
    632            C
    633 
    634     Since nothing depends on `A` or `B`, the root node guarantees that
    635     these two verifiers will both be called and properly logged.
    636 
    637     The root node is not directly accessible; however repair actions can
    638     trigger on it by using `RepairStrategy.ROOT_TAG`.  Additionally, the
    639     node will be logged in `status.log` whenever `verify()` succeeds.
    640 
    641     # The Repair Actions List
    642     The list of repair actions is constructed from the second argument
    643     passed to the passed to the `RepairStrategy` constructor.  That
    644     argument is an iterable consisting of four-element tuples in the
    645     form `(constructor, tag, deps, triggers)`:
    646       * The `constructor` value is a callable that creates a
    647         `RepairAction` as for the interface of the class constructor.
    648         For classes that inherit the default constructor from
    649         `RepairAction`, this can be the class itself.
    650       * The `tag` value is the tag to be associated with the constructed
    651         repair action.
    652       * The `deps` value is an iterable (e.g. list or tuple) of strings.
    653         Each string corresponds to the `tag` member of a `Verifier` that
    654         the repair action depends on.
    655       * The `triggers` value is an iterable (e.g. list or tuple) of
    656         strings.  Each string corresponds to the `tag` member of a
    657         `Verifier` that can trigger the repair action.
    658 
    659     `RepairStrategy` deps and triggers can only refer to verifiers,
    660     not to other repair actions.
    661     """
    662 
    663     # This name is reserved; clients may not use it.
    664     ROOT_TAG = 'PASS'
    665 
    666     @staticmethod
    667     def _add_verifier(verifiers, constructor, tag, dep_tags):
    668         """
    669         Construct and remember a verifier.
    670 
    671         Create a `Verifier` using `constructor` and `tag`.  Dependencies
    672         for construction are found by looking up `dep_tags` in the
    673         `verifiers` dictionary.
    674 
    675         After construction, the new verifier is added to `verifiers`.
    676 
    677         @param verifiers    Dictionary of verifiers, indexed by tag.
    678         @param constructor  Verifier construction function.
    679         @param tag          Tag parameter for the construction function.
    680         @param dep_tags     Tags of dependencies for the constructor, to
    681                             be found in `verifiers`.
    682         """
    683         assert tag not in verifiers
    684         deps = [verifiers[d] for d in dep_tags]
    685         verifiers[tag] = constructor(tag, deps)
    686 
    687     def __init__(self, verifier_data, repair_data):
    688         """
    689         Construct a `RepairStrategy` from simplified DAG data.
    690 
    691         The input `verifier_data` object describes how to construct
    692         verify nodes and the dependencies that relate them, as detailed
    693         above.
    694 
    695         The input `repair_data` object describes how to construct repair
    696         actions and their dependencies and triggers, as detailed above.
    697 
    698         @param verifier_data  Iterable value with constructors for the
    699                               elements of the verification DAG and their
    700                               dependencies.
    701         @param repair_data    Iterable value with constructors for the
    702                               elements of the repair action list, and
    703                               their dependencies and triggers.
    704         """
    705         # Metrics - we report on 'actions' for every repair action
    706         # we execute; we report on 'completions' for every complete
    707         # repair operation.
    708         self._completions_counter = metrics.Counter(
    709                 'chromeos/autotest/repair/completions')
    710         self._actions_counter = metrics.Counter(
    711                 'chromeos/autotest/repair/actions')
    712         # We use the `all_verifiers` list to guarantee that our root
    713         # verifier will execute its dependencies in the order provided
    714         # to us by our caller.
    715         verifier_map = {}
    716         all_tags = []
    717         dependencies = set()
    718         for constructor, tag, deps in verifier_data:
    719             self._add_verifier(verifier_map, constructor, tag, deps)
    720             dependencies.update(deps)
    721             all_tags.append(tag)
    722         # Capture all the verifiers that have nothing depending on them.
    723         root_tags = [t for t in all_tags if t not in dependencies]
    724         self._add_verifier(verifier_map, _RootVerifier,
    725                            self.ROOT_TAG, root_tags)
    726         self._verify_root = verifier_map[self.ROOT_TAG]
    727         self._repair_actions = []
    728         for constructor, tag, deps, triggers in repair_data:
    729             r = constructor(tag,
    730                             [verifier_map[d] for d in deps],
    731                             [verifier_map[t] for t in triggers])
    732             self._repair_actions.append(r)
    733 
    734     def _count_completions(self, host, success):
    735         try:
    736             board = host.host_info_store.board or ''
    737         except Exception:
    738             board = ''
    739         fields = {'success': success, 'board': board}
    740         self._completions_counter.increment(fields=fields)
    741         for ra in self._repair_actions:
    742             fields = {'tag': ra.tag,
    743                       'status': ra.status,
    744                       'success': success,
    745                       'board': board}
    746             self._actions_counter.increment(fields=fields)
    747 
    748     def verify(self, host, silent=False):
    749         """
    750         Run the verifier DAG on the given host.
    751 
    752         @param host     The target to be verified.
    753         @param silent   If true, don't log host status records.
    754         """
    755         self._verify_root._reverify()
    756         self._verify_root._verify_host(host, silent)
    757 
    758     def repair(self, host, silent=False):
    759         """
    760         Run the repair list on the given host.
    761 
    762         @param host     The target to be repaired.
    763         @param silent   If true, don't log host status records.
    764         """
    765         self._verify_root._reverify()
    766         for ra in self._repair_actions:
    767             try:
    768                 ra._repair_host(host, silent)
    769             except Exception as e:
    770                 # all logging and exception handling was done at
    771                 # lower levels
    772                 pass
    773         try:
    774             self._verify_root._verify_host(host, silent)
    775         except:
    776             self._count_completions(host, False)
    777             raise
    778         self._count_completions(host, True)
    779