blob: 49506185fbc6898f1139935474a7c149d8bc3d77 [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
27class 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
37class 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
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.
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 """
127 failures = []
128 for v in verifiers:
129 try:
130 v._verify_host(host)
131 except AutotestVerifyDependencyError as e:
132 failures.extend(e.args)
133 except Exception as e:
134 failures.append(v.description)
135 if failures:
136 raise AutotestVerifyDependencyError(*failures)
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
274class _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
305class RepairStrategy(object):
306 """
307 A class for organizing `Verifier` objects.
308
309 An instance of `RepairStrategy` is organized as a set of `Verifier`
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.
313 """
314
315 def __init__(self, verifier_data):
316 """
317 Construct a `RepairStrategy` from simplified DAG data.
318
319 The input `verifier_data` object describes how to construct
320 verify nodes and the dependencies that relate them, as detailed
321 below.
322
323 The `verifier_data` parameter is an iterable object (e.g. a list
324 or tuple) of entries. Each entry is a two-element iterable of
325 the form `(constructor, deps)`:
326 * The `constructor` value is a callable that creates a
327 `Verifier` as for the interface of the default constructor.
328 For classes that inherit the default constructor from
329 `Verifier`, this can be the class itself.
330 * The `deps` value is an iterable (e.g. list or tuple) of
331 strings. Each string corresponds to the `tag` member of a
332 `Verifier` dependency.
333
334 Note that `verifier_data` *must* be listed in dependency order.
335 That is, if `B` depends on `A`, then the constructor for `A`
336 must precede the constructor for `B` in the list. Put another
337 way, the following is valid `verifier_data`:
338
339 ```
340 ((AlphaVerifier, ()), (BetaVerifier, ('alpha',)))
341 ```
342
343 The following will fail at construction time:
344
345 ```
346 ((BetaVerifier, ('alpha',)), (AlphaVerifier, ()))
347 ```
348
349 @param verifier_data Iterable value with constructors for the
350 elements of the verification DAG and their
351 dependencies.
352 """
353 verifier_map = {}
354 roots = set()
355 for construct, deps in verifier_data:
356 v = construct([verifier_map[d] for d in deps])
357 verifier_map[v.tag] = v
358 roots.add(v)
359 roots.difference_update(deps)
360 self._verify_root = _RootVerifier(list(roots), 'all')
361
362
363 def verify(self, host):
364 """
365 Run the verifier DAG on the given host.
366
367 @param host The target to be verified.
368 """
369 self._verify_root._reverify()
370 self._verify_root._verify_host(host)