J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 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 in Autotest. |
| 7 | |
| 8 | The framework provides implementation code in support of |
| 9 | `Host.verify()` used in Verify special tasks. |
| 10 | |
| 11 | The framework consists of these classes: |
| 12 | * `Verifier`: A class representing a single verification check. |
| 13 | * `RepairStrategy`: A class for organizing a collection of |
| 14 | `Verifier` instances, and invoking them in order. |
| 15 | |
| 16 | Individual operations during verification are handled by instances of |
| 17 | `Verifier`. `Verifier` objects are meant to test for specific |
| 18 | conditions that may cause tests to fail. |
| 19 | """ |
| 20 | |
| 21 | import logging |
| 22 | |
| 23 | import common |
| 24 | from autotest_lib.client.common_lib import error |
| 25 | |
| 26 | |
| 27 | class AutotestHostVerifyError(error.AutotestError): |
| 28 | """ |
| 29 | Generic Exception for failures from `Verifier` objects. |
| 30 | |
| 31 | Instances of this exception can be raised when a `verify()` |
| 32 | method fails, if no more specific exception is available. |
| 33 | """ |
| 34 | pass |
| 35 | |
| 36 | |
| 37 | class AutotestVerifyDependencyError(error.AutotestError): |
| 38 | """ |
| 39 | Exception raised for failures in dependencies. |
| 40 | |
| 41 | This exception is used to distinguish an original failure from a |
| 42 | failure being passed back from a verification dependency. That is, |
| 43 | if 'B' depends on 'A', and 'A' fails, 'B' will raise this exception |
| 44 | to signal that the original failure is further down the dependency |
| 45 | chain. |
| 46 | |
| 47 | Each argument to the constructor for this class should be the string |
| 48 | description of one failed dependency. |
| 49 | |
| 50 | `Verifier._verify_host()` recognizes and handles this exception |
| 51 | specially. |
| 52 | """ |
| 53 | pass |
| 54 | |
| 55 | |
| 56 | class Verifier(object): |
| 57 | """ |
| 58 | Abstract class embodying one verification check. |
| 59 | |
| 60 | A concrete subclass of `Verifier` provides a simple check that can |
| 61 | determine a host's fitness for testing. Failure indicates that the |
| 62 | check found a problem that can cause at least one test to fail. |
| 63 | |
| 64 | `Verifier` objects are organized in a DAG identifying dependencies |
| 65 | among operations. The DAG controls ordering and prevents wasted |
| 66 | effort: If verification operation V2 requires that verification |
| 67 | operation V1 pass, then a) V1 will run before V2, and b) if V1 |
| 68 | fails, V2 won't run at all. The `_verify_host()` method ensures |
| 69 | that all dependencies run and pass before invoking the `verify()` |
| 70 | method. |
| 71 | |
| 72 | A `Verifier` object caches its result the first time it calls |
| 73 | `verify()`. Subsequent calls return the cached result, without |
| 74 | re-running the check code. The `_reverify()` method clears the |
| 75 | cached result in the current node, and in all dependencies. |
| 76 | |
| 77 | Subclasses must supply these properties and methods: |
| 78 | * `verify()`: This is the method to perform the actual |
| 79 | verification check. |
| 80 | * `tag`: This property is a short string to uniquely identify |
| 81 | this verifier in `status.log` records. |
| 82 | * `description`: This is a property with a one-line summary of |
| 83 | the verification check to be performed. This string is used to |
| 84 | identify the verifier in debug logs. |
| 85 | Subclasses must override all of the above attributes; subclasses |
| 86 | should not override or extend any other attributes of this class. |
| 87 | |
| 88 | The base class manages the following private data: |
| 89 | * `_result`: The cached result of verification. |
| 90 | * `_dependency_list`: The list of dependencies. |
| 91 | Subclasses should not use these attributes. |
| 92 | |
| 93 | @property tag Short identifier to be used in logging. |
| 94 | @property description Text summary of the verification check. |
| 95 | @property _result Cached result of verification. |
| 96 | @property _dependency_list Dependency pre-requisites. |
| 97 | """ |
| 98 | |
| 99 | def __init__(self, dependencies): |
| 100 | self._result = None |
| 101 | self._dependency_list = dependencies |
| 102 | self._verify_tag = 'verify.' + self.tag |
| 103 | |
| 104 | |
| 105 | def _verify_list(self, host, verifiers): |
| 106 | """ |
| 107 | Test a list of verifiers against a given host. |
| 108 | |
| 109 | This invokes `_verify_host()` on every verifier in the given |
| 110 | list. If any verifier in the transitive closure of dependencies |
| 111 | in the list fails, an `AutotestVerifyDependencyError` is raised |
| 112 | containing the description of each failed verifier. Only |
| 113 | original failures are reported; verifiers that don't run due |
| 114 | to a failed dependency are omitted. |
| 115 | |
| 116 | By design, original failures are logged once in `_verify_host()` |
| 117 | when `verify()` originally fails. The additional data gathered |
| 118 | here is for the debug logs to indicate why a subsequent |
| 119 | operation never ran. |
| 120 | |
| 121 | @param host The host to be tested against the verifiers. |
| 122 | @param verifiers List of verifiers to be checked. |
| 123 | |
| 124 | @raises AutotestVerifyDependencyError Raised when at least |
| 125 | one verifier in the list has failed. |
| 126 | """ |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 127 | failures = set() |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 128 | for v in verifiers: |
| 129 | try: |
| 130 | v._verify_host(host) |
| 131 | except AutotestVerifyDependencyError as e: |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 132 | failures.update(e.args) |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 133 | except Exception as e: |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 134 | failures.add(v.description) |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 135 | if failures: |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 136 | raise AutotestVerifyDependencyError(*list(failures)) |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 137 | |
| 138 | |
| 139 | def _reverify(self): |
| 140 | """ |
| 141 | Discard cached verification results. |
| 142 | |
| 143 | Reset the cached verification result for this node, and for the |
| 144 | transitive closure of all dependencies. |
| 145 | """ |
| 146 | if self._result is not None: |
| 147 | self._result = None |
| 148 | for v in self._dependency_list: |
| 149 | v._reverify() |
| 150 | |
| 151 | |
| 152 | def _verify_host(self, host): |
| 153 | """ |
| 154 | Determine the result of verification, and log results. |
| 155 | |
| 156 | If this verifier does not have a cached verification result, |
| 157 | check dependencies, and if they pass, run `verify()`. Log |
| 158 | informational messages regarding failed dependencies. If we |
| 159 | call `verify()`, log the result in `status.log`. |
| 160 | |
| 161 | If we already have a cached result, return that result without |
| 162 | logging any message. |
| 163 | |
| 164 | @param host The host to be tested for a problem. |
| 165 | """ |
| 166 | if self._result is not None: |
| 167 | if isinstance(self._result, Exception): |
| 168 | raise self._result # cached failure |
| 169 | elif self._result: |
| 170 | return # cached success |
| 171 | self._result = False |
| 172 | try: |
| 173 | self._verify_list(host, self._dependency_list) |
| 174 | except AutotestVerifyDependencyError as e: |
| 175 | logging.info('Dependencies failed; skipping this ' |
| 176 | 'operation: %s', self.description) |
| 177 | for description in e.args: |
| 178 | logging.debug(' %s', description) |
| 179 | raise |
| 180 | # TODO(jrbarnette): this message also logged for |
| 181 | # RepairAction; do we want to customize that message? |
| 182 | logging.info('Verifying this condition: %s', self.description) |
| 183 | try: |
| 184 | self.verify(host) |
| 185 | host.record("GOOD", None, self._verify_tag) |
| 186 | except Exception as e: |
| 187 | logging.exception('Failed: %s', self.description) |
| 188 | self._result = e |
| 189 | host.record("FAIL", None, self._verify_tag, str(e)) |
| 190 | raise |
| 191 | self._result = True |
| 192 | |
| 193 | |
| 194 | def verify(self, host): |
| 195 | """ |
| 196 | Unconditionally perform a verification check. |
| 197 | |
| 198 | This method is responsible for testing for a single problem on a |
| 199 | host. Implementations should follow these guidelines: |
| 200 | * The check should find a problem that will cause testing to |
| 201 | fail. |
| 202 | * Verification checks on a working system should run quickly |
| 203 | and should be optimized for success; a check that passes |
| 204 | should finish within seconds. |
| 205 | * Verification checks are not expected have side effects, but |
| 206 | may apply trivial fixes if they will finish within the time |
| 207 | constraints above. |
| 208 | |
| 209 | A verification check should normally trigger a single set of |
| 210 | repair actions. If two different failures can require two |
| 211 | different repairs, ideally they should use two different |
| 212 | subclasses of `Verifier`. |
| 213 | |
| 214 | Implementations indicate failure by raising an exception. The |
| 215 | exception text should be a short, 1-line summary of the error. |
| 216 | The text should be concise and diagnostic, as it will appear in |
| 217 | `status.log` files. |
| 218 | |
| 219 | If this method finds no problems, it returns without raising any |
| 220 | exception. |
| 221 | |
| 222 | Implementations should avoid most logging actions, but can log |
| 223 | DEBUG level messages if they provide significant information for |
| 224 | diagnosing failures. |
| 225 | |
| 226 | @param host The host to be tested for a problem. |
| 227 | """ |
| 228 | raise NotImplementedError('Class %s does not implement ' |
| 229 | 'verify()' % type(self).__name__) |
| 230 | |
| 231 | |
| 232 | @property |
| 233 | def tag(self): |
| 234 | """ |
| 235 | Tag for use in logging status records. |
| 236 | |
| 237 | This is a property with a short string used to identify the |
| 238 | verification check in the 'status.log' file. The tag should |
| 239 | contain only lower case letters, digits, and '_' characters. |
| 240 | This tag is not used alone, but is combined with other |
| 241 | identifiers, based on the operation being logged. |
| 242 | |
| 243 | N.B. Subclasses are required to override this method, but |
| 244 | we _don't_ raise NotImplementedError here. `_verify_host()` |
| 245 | fails in inscrutable ways if this method raises any |
| 246 | exception, so for debug purposes, it's better to return a |
| 247 | default value. |
| 248 | |
| 249 | @return A short identifier-like string. |
| 250 | """ |
| 251 | return 'bogus__%s' % type(self).__name__ |
| 252 | |
| 253 | |
| 254 | @property |
| 255 | def description(self): |
| 256 | """ |
| 257 | Text description of this verifier for log messages. |
| 258 | |
| 259 | This string will be logged with failures, and should |
| 260 | describe the condition required for success. |
| 261 | |
| 262 | N.B. Subclasses are required to override this method, but |
| 263 | we _don't_ raise NotImplementedError here. `_verify_host()` |
| 264 | fails in inscrutable ways if this method raises any |
| 265 | exception, so for debug purposes, it's better to return a |
| 266 | default value. |
| 267 | |
| 268 | @return A descriptive string. |
| 269 | """ |
| 270 | return ('Class %s fails to implement description().' % |
| 271 | type(self).__name__) |
| 272 | |
| 273 | |
| 274 | class _RootVerifier(Verifier): |
| 275 | """ |
| 276 | Utility class used by `RepairStrategy`. |
| 277 | |
| 278 | A node of this class by itself does nothing; it always passes (if it |
| 279 | can run). This class exists merely to be the root of a DAG of |
| 280 | dependencies in an instance of `RepairStrategy`. |
| 281 | """ |
| 282 | |
| 283 | def __init__(self, dependencies, tag): |
| 284 | # N.B. must initialize _tag before calling superclass, |
| 285 | # because the superclass constructor uses `self.tag`. |
| 286 | self._tag = tag |
| 287 | super(_RootVerifier, self).__init__(dependencies) |
| 288 | |
| 289 | |
| 290 | def verify(self, host): |
| 291 | pass |
| 292 | |
| 293 | |
| 294 | @property |
| 295 | def tag(self): |
| 296 | return self._tag |
| 297 | |
| 298 | |
| 299 | @property |
| 300 | def description(self): |
| 301 | return 'All host verification checks pass' |
| 302 | |
| 303 | |
| 304 | |
| 305 | class RepairStrategy(object): |
| 306 | """ |
| 307 | A class for organizing `Verifier` objects. |
| 308 | |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 309 | An instance of `RepairStrategy` is organized as a DAG of `Verifier` |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 310 | objects. The class provides methods for invoking those objects in |
| 311 | order, when needed: the `verify()` method walks the verifier DAG in |
| 312 | dependency order. |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 313 | |
| 314 | The verifier DAG is constructed from a tuple (or any iterable) |
| 315 | passed to the `RepairStrategy` constructor. Each entry is a |
| 316 | two-element iterable of the form `(constructor, deps)`: |
| 317 | * The `constructor` value is a callable that creates a `Verifier` |
| 318 | as for the interface of the default constructor. For classes |
| 319 | that inherit the default constructor from `Verifier`, this can |
| 320 | be the class itself. |
| 321 | * The `deps` value is an iterable (e.g. list or tuple) of strings. |
| 322 | Each string corresponds to the `tag` member of a `Verifier` |
| 323 | dependency. |
| 324 | |
| 325 | The tag names of verifiers in the constructed DAG must all be |
| 326 | unique. The verifier tag name `'PASS'` is reserved and may not be |
| 327 | used by any verifier. |
| 328 | |
| 329 | In the input data for the constructor, dependencies must appear |
| 330 | before the nodes that depend on them. The entry below is valid: |
| 331 | |
| 332 | ((A, ()), (B, ('a',))) |
| 333 | |
| 334 | The following will fail at construction time: |
| 335 | |
| 336 | ((B, ('a',)), (A, ())) |
| 337 | |
| 338 | Internally, the DAG of verifiers is given unique root node. So, |
| 339 | given this input: |
| 340 | |
| 341 | ((C, ()), (A, ('c',)), (B, ('c',))) |
| 342 | |
| 343 | The following DAG is constructed: |
| 344 | |
| 345 | Root |
| 346 | / \ |
| 347 | A B |
| 348 | \ / |
| 349 | C |
| 350 | |
| 351 | Since nothing depends on `A` or `B`, the root node guarantees that |
| 352 | these two verifiers will both be called and properly logged. |
| 353 | |
| 354 | The root node is not part of the public interface, but it _is_ |
| 355 | logged in `status.log` whenever `verify()` succeeds. |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 356 | """ |
| 357 | |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 358 | _ROOT_TAG = 'PASS' |
| 359 | |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 360 | def __init__(self, verifier_data): |
| 361 | """ |
| 362 | Construct a `RepairStrategy` from simplified DAG data. |
| 363 | |
| 364 | The input `verifier_data` object describes how to construct |
| 365 | verify nodes and the dependencies that relate them, as detailed |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 366 | above. |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 367 | |
| 368 | @param verifier_data Iterable value with constructors for the |
| 369 | elements of the verification DAG and their |
| 370 | dependencies. |
| 371 | """ |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 372 | # We use the `all_verifiers` list to guarantee that our root |
| 373 | # verifier will execute its dependencies in the order provided |
| 374 | # to us by our caller. |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 375 | verifier_map = {} |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 376 | all_verifiers = [] |
| 377 | dependencies = set() |
| 378 | for construct, dep_tags in verifier_data: |
| 379 | deps = [verifier_map[d] for d in dep_tags] |
| 380 | dependencies.update(deps) |
| 381 | v = construct(deps) |
| 382 | assert v.tag not in verifier_map |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 383 | verifier_map[v.tag] = v |
J. Richard Barnette | 2654c55 | 2016-03-01 17:26:35 -0800 | [diff] [blame] | 384 | all_verifiers.append(v) |
| 385 | assert self._ROOT_TAG not in verifier_map |
| 386 | # Capture all the verifiers that have nothing depending on them. |
| 387 | self._verify_root = _RootVerifier( |
| 388 | [v for v in all_verifiers if v not in dependencies], |
| 389 | self._ROOT_TAG) |
J. Richard Barnette | c542c43 | 2016-02-12 11:29:34 -0800 | [diff] [blame] | 390 | |
| 391 | |
| 392 | def verify(self, host): |
| 393 | """ |
| 394 | Run the verifier DAG on the given host. |
| 395 | |
| 396 | @param host The target to be verified. |
| 397 | """ |
| 398 | self._verify_root._reverify() |
| 399 | self._verify_root._verify_host(host) |