blob: 5843f5d96518712d0c540096e87b6bd97cdf04bb [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"""
6Framework for host verification in Autotest.
7
8The framework provides implementation code in support of
9`Host.verify()` used in Verify special tasks.
10
11The 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
16Individual operations during verification are handled by instances of
17`Verifier`. `Verifier` objects are meant to test for specific
18conditions that may cause tests to fail.
19"""
20
21import logging
22
23import common
24from autotest_lib.client.common_lib import error
25
26
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -070027class AutoservVerifyError(error.AutoservError):
J. Richard Barnettec542c432016-02-12 11:29:34 -080028 """
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
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -070037class AutoservVerifyDependencyError(error.AutoservError):
J. Richard Barnettec542c432016-02-12 11:29:34 -080038 """
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
56class 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.
J. Richard Barnettec542c432016-02-12 11:29:34 -080080 * `description`: This is a property with a one-line summary of
81 the verification check to be performed. This string is used to
82 identify the verifier in debug logs.
83 Subclasses must override all of the above attributes; subclasses
84 should not override or extend any other attributes of this class.
85
86 The base class manages the following private data:
87 * `_result`: The cached result of verification.
88 * `_dependency_list`: The list of dependencies.
89 Subclasses should not use these attributes.
90
91 @property tag Short identifier to be used in logging.
92 @property description Text summary of the verification check.
93 @property _result Cached result of verification.
94 @property _dependency_list Dependency pre-requisites.
95 """
96
J. Richard Barnettea4784062016-03-04 14:14:10 -080097 def __init__(self, tag, dependencies):
J. Richard Barnettec542c432016-02-12 11:29:34 -080098 self._result = None
99 self._dependency_list = dependencies
J. Richard Barnettea4784062016-03-04 14:14:10 -0800100 self._tag = tag
J. Richard Barnettec542c432016-02-12 11:29:34 -0800101 self._verify_tag = 'verify.' + self.tag
102
103
104 def _verify_list(self, host, verifiers):
105 """
106 Test a list of verifiers against a given host.
107
108 This invokes `_verify_host()` on every verifier in the given
109 list. If any verifier in the transitive closure of dependencies
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700110 in the list fails, an `AutoservVerifyDependencyError` is raised
J. Richard Barnettec542c432016-02-12 11:29:34 -0800111 containing the description of each failed verifier. Only
112 original failures are reported; verifiers that don't run due
113 to a failed dependency are omitted.
114
115 By design, original failures are logged once in `_verify_host()`
116 when `verify()` originally fails. The additional data gathered
117 here is for the debug logs to indicate why a subsequent
118 operation never ran.
119
120 @param host The host to be tested against the verifiers.
121 @param verifiers List of verifiers to be checked.
122
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700123 @raises AutoservVerifyDependencyError Raised when at least
J. Richard Barnettec542c432016-02-12 11:29:34 -0800124 one verifier in the list has failed.
125 """
J. Richard Barnette2654c552016-03-01 17:26:35 -0800126 failures = set()
J. Richard Barnettec542c432016-02-12 11:29:34 -0800127 for v in verifiers:
128 try:
129 v._verify_host(host)
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700130 except AutoservVerifyDependencyError as e:
J. Richard Barnette2654c552016-03-01 17:26:35 -0800131 failures.update(e.args)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800132 except Exception as e:
J. Richard Barnette2654c552016-03-01 17:26:35 -0800133 failures.add(v.description)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800134 if failures:
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700135 raise AutoservVerifyDependencyError(*list(failures))
J. Richard Barnettec542c432016-02-12 11:29:34 -0800136
137
138 def _reverify(self):
139 """
140 Discard cached verification results.
141
142 Reset the cached verification result for this node, and for the
143 transitive closure of all dependencies.
144 """
145 if self._result is not None:
146 self._result = None
147 for v in self._dependency_list:
148 v._reverify()
149
150
151 def _verify_host(self, host):
152 """
153 Determine the result of verification, and log results.
154
155 If this verifier does not have a cached verification result,
156 check dependencies, and if they pass, run `verify()`. Log
157 informational messages regarding failed dependencies. If we
158 call `verify()`, log the result in `status.log`.
159
160 If we already have a cached result, return that result without
161 logging any message.
162
163 @param host The host to be tested for a problem.
164 """
165 if self._result is not None:
166 if isinstance(self._result, Exception):
167 raise self._result # cached failure
168 elif self._result:
169 return # cached success
170 self._result = False
171 try:
172 self._verify_list(host, self._dependency_list)
J. Richard Barnettecfe6b0d2016-03-17 11:39:12 -0700173 except AutoservVerifyDependencyError as e:
J. Richard Barnettec542c432016-02-12 11:29:34 -0800174 logging.info('Dependencies failed; skipping this '
175 'operation: %s', self.description)
176 for description in e.args:
177 logging.debug(' %s', description)
178 raise
179 # TODO(jrbarnette): this message also logged for
180 # RepairAction; do we want to customize that message?
181 logging.info('Verifying this condition: %s', self.description)
182 try:
183 self.verify(host)
184 host.record("GOOD", None, self._verify_tag)
185 except Exception as e:
186 logging.exception('Failed: %s', self.description)
187 self._result = e
188 host.record("FAIL", None, self._verify_tag, str(e))
189 raise
190 self._result = True
191
192
193 def verify(self, host):
194 """
195 Unconditionally perform a verification check.
196
197 This method is responsible for testing for a single problem on a
198 host. Implementations should follow these guidelines:
199 * The check should find a problem that will cause testing to
200 fail.
201 * Verification checks on a working system should run quickly
202 and should be optimized for success; a check that passes
203 should finish within seconds.
204 * Verification checks are not expected have side effects, but
205 may apply trivial fixes if they will finish within the time
206 constraints above.
207
208 A verification check should normally trigger a single set of
209 repair actions. If two different failures can require two
210 different repairs, ideally they should use two different
211 subclasses of `Verifier`.
212
213 Implementations indicate failure by raising an exception. The
214 exception text should be a short, 1-line summary of the error.
215 The text should be concise and diagnostic, as it will appear in
216 `status.log` files.
217
218 If this method finds no problems, it returns without raising any
219 exception.
220
221 Implementations should avoid most logging actions, but can log
222 DEBUG level messages if they provide significant information for
223 diagnosing failures.
224
225 @param host The host to be tested for a problem.
226 """
227 raise NotImplementedError('Class %s does not implement '
228 'verify()' % type(self).__name__)
229
230
231 @property
232 def tag(self):
233 """
234 Tag for use in logging status records.
235
236 This is a property with a short string used to identify the
237 verification check in the 'status.log' file. The tag should
J. Richard Barnettea4784062016-03-04 14:14:10 -0800238 contain only letters, digits, and '_' characters. This tag is
239 not used alone, but is combined with other identifiers, based on
240 the operation being logged.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800241
242 @return A short identifier-like string.
243 """
J. Richard Barnettea4784062016-03-04 14:14:10 -0800244 return self._tag
J. Richard Barnettec542c432016-02-12 11:29:34 -0800245
246
247 @property
248 def description(self):
249 """
250 Text description of this verifier for log messages.
251
252 This string will be logged with failures, and should
253 describe the condition required for success.
254
255 N.B. Subclasses are required to override this method, but
256 we _don't_ raise NotImplementedError here. `_verify_host()`
257 fails in inscrutable ways if this method raises any
258 exception, so for debug purposes, it's better to return a
259 default value.
260
261 @return A descriptive string.
262 """
263 return ('Class %s fails to implement description().' %
264 type(self).__name__)
265
266
267class _RootVerifier(Verifier):
268 """
269 Utility class used by `RepairStrategy`.
270
271 A node of this class by itself does nothing; it always passes (if it
272 can run). This class exists merely to be the root of a DAG of
273 dependencies in an instance of `RepairStrategy`.
274 """
275
J. Richard Barnettec542c432016-02-12 11:29:34 -0800276 def verify(self, host):
277 pass
278
279
280 @property
J. Richard Barnettec542c432016-02-12 11:29:34 -0800281 def description(self):
282 return 'All host verification checks pass'
283
284
285
286class RepairStrategy(object):
287 """
288 A class for organizing `Verifier` objects.
289
J. Richard Barnette2654c552016-03-01 17:26:35 -0800290 An instance of `RepairStrategy` is organized as a DAG of `Verifier`
J. Richard Barnettec542c432016-02-12 11:29:34 -0800291 objects. The class provides methods for invoking those objects in
292 order, when needed: the `verify()` method walks the verifier DAG in
293 dependency order.
J. Richard Barnette2654c552016-03-01 17:26:35 -0800294
295 The verifier DAG is constructed from a tuple (or any iterable)
296 passed to the `RepairStrategy` constructor. Each entry is a
J. Richard Barnettea4784062016-03-04 14:14:10 -0800297 two-element iterable of the form `(constructor, tag, deps)`:
J. Richard Barnette2654c552016-03-01 17:26:35 -0800298 * The `constructor` value is a callable that creates a `Verifier`
299 as for the interface of the default constructor. For classes
300 that inherit the default constructor from `Verifier`, this can
301 be the class itself.
J. Richard Barnettea4784062016-03-04 14:14:10 -0800302 * The `tag` value is the tag to be associated with the constructed
303 verifier.
J. Richard Barnette2654c552016-03-01 17:26:35 -0800304 * The `deps` value is an iterable (e.g. list or tuple) of strings.
305 Each string corresponds to the `tag` member of a `Verifier`
306 dependency.
307
308 The tag names of verifiers in the constructed DAG must all be
309 unique. The verifier tag name `'PASS'` is reserved and may not be
310 used by any verifier.
311
312 In the input data for the constructor, dependencies must appear
J. Richard Barnettea4784062016-03-04 14:14:10 -0800313 before the nodes that depend on them. Thus:
J. Richard Barnette2654c552016-03-01 17:26:35 -0800314
J. Richard Barnettea4784062016-03-04 14:14:10 -0800315 ((A, 'a', ()), (B, 'b', ('a',))) # This is valid
316 ((B, 'b', ('a',)), (A, 'a', ())) # This will fail!
J. Richard Barnette2654c552016-03-01 17:26:35 -0800317
318 Internally, the DAG of verifiers is given unique root node. So,
319 given this input:
320
J. Richard Barnettea4784062016-03-04 14:14:10 -0800321 ((C, 'c', ()),
322 (A, 'a', ('c',)),
323 (B, 'b', ('c',)))
J. Richard Barnette2654c552016-03-01 17:26:35 -0800324
325 The following DAG is constructed:
326
327 Root
328 / \
329 A B
330 \ /
331 C
332
333 Since nothing depends on `A` or `B`, the root node guarantees that
334 these two verifiers will both be called and properly logged.
335
336 The root node is not part of the public interface, but it _is_
337 logged in `status.log` whenever `verify()` succeeds.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800338 """
339
J. Richard Barnettea4784062016-03-04 14:14:10 -0800340 # This name is reserved; clients may not use it.
J. Richard Barnette2654c552016-03-01 17:26:35 -0800341 _ROOT_TAG = 'PASS'
342
J. Richard Barnettec542c432016-02-12 11:29:34 -0800343 def __init__(self, verifier_data):
344 """
345 Construct a `RepairStrategy` from simplified DAG data.
346
347 The input `verifier_data` object describes how to construct
348 verify nodes and the dependencies that relate them, as detailed
J. Richard Barnette2654c552016-03-01 17:26:35 -0800349 above.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800350
351 @param verifier_data Iterable value with constructors for the
352 elements of the verification DAG and their
353 dependencies.
354 """
J. Richard Barnette2654c552016-03-01 17:26:35 -0800355 # We use the `all_verifiers` list to guarantee that our root
356 # verifier will execute its dependencies in the order provided
357 # to us by our caller.
J. Richard Barnettec542c432016-02-12 11:29:34 -0800358 verifier_map = {}
J. Richard Barnette2654c552016-03-01 17:26:35 -0800359 all_verifiers = []
360 dependencies = set()
J. Richard Barnettea4784062016-03-04 14:14:10 -0800361 for constructor, tag, dep_tags in verifier_data:
362 assert tag not in verifier_map
J. Richard Barnette2654c552016-03-01 17:26:35 -0800363 deps = [verifier_map[d] for d in dep_tags]
364 dependencies.update(deps)
J. Richard Barnettea4784062016-03-04 14:14:10 -0800365 v = constructor(tag, deps)
366 verifier_map[tag] = v
J. Richard Barnette2654c552016-03-01 17:26:35 -0800367 all_verifiers.append(v)
368 assert self._ROOT_TAG not in verifier_map
369 # Capture all the verifiers that have nothing depending on them.
J. Richard Barnettea4784062016-03-04 14:14:10 -0800370 root_list = [v for v in all_verifiers if v not in dependencies]
371 self._verify_root = _RootVerifier(self._ROOT_TAG, root_list)
J. Richard Barnettec542c432016-02-12 11:29:34 -0800372
373
374 def verify(self, host):
375 """
376 Run the verifier DAG on the given host.
377
378 @param host The target to be verified.
379 """
380 self._verify_root._reverify()
381 self._verify_root._verify_host(host)