• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 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
5import logging
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.common_lib.cros import servo_afe_board_map
9from autotest_lib.server import test
10from autotest_lib.server.cros.servo import servo
11
12
13def _successful(result_value):
14    return result_value and not isinstance(result_value, Exception)
15
16
17class _DiagnosticTest(object):
18    """Data needed to handle one diagnostic test on a Servo host.
19
20    The class encapsulates two basic elements:
21     1. A pre-requisite test that must have passed.  The
22        pre-requisite is recorded as a key in the results
23        dictionary.
24     2. A function that performs the actual diagnostic test.
25
26    All tests have the implicit pre-requisite that the servo host
27    can be reached on the network via ping.
28
29    Pre-requisites are meant to capture relationships of the form
30    "if test X cant't pass, test Y will always fail".  Typically,
31    that means that test X tests a capability used by test Y.
32
33    This implementation is a bit naive:  It assumes only a single
34    pre-requisite, and it assumes the only outcome is a simple
35    pass/fail.  The design also doesn't account for relationships
36    of the form "if test X fails, run test Y to try and distinguish
37    possible causes".
38
39    """
40
41    def __init__(self, prerequisite, get_result):
42        self._prerequisite = prerequisite
43        self._get_result = get_result
44
45    def can_run(self, results):
46        """Return whether this test's pre-requisite is satisfied.
47
48        @param results The results dictionary with the status of
49                       this test's pre-requisite.
50
51        """
52        if self._prerequisite is None:
53            return True
54        return _successful(results[self._prerequisite])
55
56    def run_diagnostic(self, servo_host, servod):
57        """Run the diagnostic test, and return the result.
58
59        The test receives ServoHost and Servo objects to be tested;
60        typically a single test uses one or the other, but not both.
61
62        @param servo_host A ServoHost object to be the target of the
63                          test.
64        @param servod     A Servo object to be the target of the
65                          test.
66        @return If the test returns normally, return its result.  If
67                the test raises an exception, return the exception.
68
69        """
70        try:
71            return self._get_result(servo_host, servod)
72        except Exception as e:
73            return e
74
75
76def _ssh_test(servo_host, servod):
77    """Test whether the servo host answers to ssh.
78
79    This test serves as a basic pre-requisite for tests that
80    use ssh to test other conditions.
81
82    Pre-requisite: There are no pre-requisites for this test aside
83    from the implicit pre-requisite that the host answer to ping.
84
85    @param servo_host The ServoHost object to talk to via ssh.
86    @param servod     Ignored.
87
88    """
89    return servo_host.is_up()
90
91
92def _servod_connect(servo_host, servod):
93    """Test whether connection to servod succeeds.
94
95    This tests the connection to the target servod with a simple
96    method call.  As a side-effect, all hardware signals are
97    initialized to default values.
98
99    This function always returns success.  The test can only fail if
100    the underlying call to servo raises an exception.
101
102    Pre-requisite: There are no pre-requisites for this test aside
103    from the implicit pre-requisite that the host answer to ping.
104
105    @return `True`
106
107    """
108    # TODO(jrbarnette) We need to protect this call so that it
109    # will time out if servod doesn't respond.
110    servod.initialize_dut()
111    return True
112
113
114def _pwr_button_test(servo_host, servod):
115    """Test whether the 'pwr_button' signal is correct.
116
117    This tests whether the state of the 'pwr_button' signal is
118    'release'.  When the servo flex cable is not attached, the
119    signal will be stuck at 'press'.
120
121    Pre-requisite:  This test depends on successful initialization
122    of servod.
123
124    Rationale:  The initialization step sets 'pwr_button' to
125    'release', which is required to justify the expectations of this
126    test.  Also, if initialization fails, we can reasonably expect
127    that all communication with servod will fail.
128
129    @param servo_host Ignored.
130    @param servod     The Servo object to be tested.
131
132    """
133    return servod.get('pwr_button') == 'release'
134
135
136def _lid_test(servo_host, servod):
137    """Test whether the 'lid_open' signal is correct.
138
139    This tests whether the state of the 'lid_open' signal has a
140    correct value.  There is a manual switch on the servo board; if
141    that switch is set wrong, the signal will be stuck at 'no'.
142    Working units may return a setting of 'yes' (meaning the lid is
143    open) or 'not_applicable' (meaning the device has no lid).
144
145    Pre-requisite:  This test depends on the 'pwr_button' test.
146
147    Rationale:  If the 'pwr_button' test fails, the flex cable may
148    be disconnected, which means any servo operation to read a
149    hardware signal will fail.
150
151    @param servo_host Ignored.
152    @param servod     The Servo object to be tested.
153
154    """
155    return servod.get('lid_open') != 'no'
156
157
158def _command_test(servo_host, command):
159    """Utility to return the output of a command on a servo host.
160
161    The command is expected to produce at most one line of
162    output.  A trailing newline, if any, is stripped.
163
164    @return Output from the command with the trailing newline
165            removed.
166
167    """
168    return servo_host.run(command).stdout.strip('\n')
169
170
171def _brillo_test(servo_host, servod):
172    """Get the version of Brillo running on the servo host.
173
174    Reads the setting of CHROMEOS_RELEASE_VERSION from
175    /etc/lsb-release on the servo host.  An empty string will
176    returned if there is no such setting.
177
178    Pre-requisite:  This test depends on the ssh test.
179
180    @param servo_host The ServoHost object to be queried.
181    @param servod     Ignored.
182
183    @return Returns a Brillo version number or an empty string.
184
185    """
186    command = ('sed "s/CHROMEOS_RELEASE_VERSION=//p ; d" '
187                   '/etc/lsb-release')
188    return _command_test(servo_host, command)
189
190
191def _board_test(servo_host, servod):
192    """Get the board for which the servo is configured.
193
194    Reads the setting of BOARD from /var/lib/servod/config.  An
195    empty string is returned if the board is unconfigured.
196
197    Pre-requisite:  This test depends on the brillo version test.
198
199    Rationale: The /var/lib/servod/config file is used by the servod
200    upstart job, which is specific to Brillo servo builds.  This
201    test has no meaning if the target servo host isn't running
202    Brillo.
203
204    @param servo_host The ServoHost object to be queried.
205    @param servod     Ignored.
206
207    @return The confgured board or an empty string.
208
209    """
210    command = ('CONFIG=/var/lib/servod/config\n'
211               '[ -f $CONFIG ] && . $CONFIG && echo $BOARD')
212    return _command_test(servo_host, command)
213
214
215def _servod_test(servo_host, servod):
216    """Get the status of the servod upstart job.
217
218    Ask upstart for the status of the 'servod' job.  Return whether
219    the job is reported running.
220
221    Pre-requisite:  This test depends on the brillo version test.
222
223    Rationale: The servod upstart job is specific to Brillo servo
224    builds.  This test has no meaning if the target servo host isn't
225    running Brillo.
226
227    @param servo_host The ServoHost object to be queried.
228    @param servod     Ignored.
229
230    @return `True` if the job is running, or `False` otherwise.
231
232    """
233    command = 'status servod | sed "s/,.*//"'
234    return _command_test(servo_host, command) == 'servod start/running'
235
236
237_DIAGNOSTICS_LIST = [
238    ('ssh_responds',
239        _DiagnosticTest(None, _ssh_test)),
240    ('servod_connect',
241        _DiagnosticTest(None, _servod_connect)),
242    ('pwr_button',
243        _DiagnosticTest('servod_connect', _pwr_button_test)),
244    ('lid_open',
245        _DiagnosticTest('pwr_button', _lid_test)),
246    ('brillo_version',
247        _DiagnosticTest('ssh_responds', _brillo_test)),
248    ('board',
249        _DiagnosticTest('brillo_version', _board_test)),
250    ('servod',
251        _DiagnosticTest('brillo_version', _servod_test)),
252]
253
254
255class infra_ServoDiagnosis(test.test):
256    """Test a servo and diagnose common failures."""
257
258    version = 1
259
260    def _run_results(self, servo_host, servod):
261        results = {}
262        for key, tester in _DIAGNOSTICS_LIST:
263            if tester.can_run(results):
264                results[key] = tester.run_diagnostic(servo_host, servod)
265                logging.info('Test %s result %s', key, results[key])
266            else:
267                results[key] = None
268                logging.info('Skipping %s', key)
269        return results
270
271    def run_once(self, host):
272        """Test and diagnose the servo for the given host.
273
274        @param host Host object for a DUT with Servo.
275
276        """
277        # TODO(jrbarnette):  Need to handle ping diagnoses:
278        #   + Specifically report if servo host isn't a lab host.
279        #   + Specifically report if servo host is in lab but
280        #     doesn't respond to ping.
281        servo_host = host._servo_host
282        servod = host.servo
283        if servod is None:
284            servod = servo.Servo(servo_host)
285        results = self._run_results(servo_host, servod)
286
287        if not _successful(results['ssh_responds']):
288            raise error.TestFail('ssh connection to %s failed' %
289                                     servo_host.hostname)
290
291        if not _successful(results['brillo_version']):
292            raise error.TestFail('Servo host %s is not running Brillo' %
293                                     servo_host.hostname)
294
295        # Make sure servo board matches DUT label
296        board = host._get_board_from_afe()
297        board = servo_afe_board_map.map_afe_board_to_servo_board(board)
298        if (board and results['board'] is not None and
299                board != results['board']):
300            logging.info('AFE says board should be %s', board)
301            if results['servod']:
302                servo_host.run('stop servod', ignore_status=True)
303            servo_host.run('start servod BOARD=%s' % board)
304            results = self._run_results(servo_host, servod)
305
306        # TODO(jrbarnette): The brillo update check currently
307        # lives in ServoHost; it needs to move here.
308
309        # Repair actions:
310        #   if servod is dead or running but not working
311        #     reboot and re-run results
312
313        if (not _successful(results['servod']) or
314                not _successful(results['servod_connect'])):
315            # TODO(jrbarnette):  For now, allow reboot failures to
316            # raise their exceptions up the stack.  This event
317            # shouldn't happen, so smarter handling should wait
318            # until we have a use case to guide the requirements.
319            servo_host.reboot()
320            results = self._run_results(servo_host, servod)
321            if not _successful(results['servod']):
322                # write result value to log
323                raise error.TestFail('servod failed to start on %s' %
324                                         servo_host.hostname)
325
326            if not _successful(results['servod_connect']):
327                raise error.TestFail('Servo failure on %s' %
328                                         servo_host.hostname)
329
330        if not _successful(results['pwr_button']):
331            raise error.TestFail('Stuck power button on %s' %
332                                     servo_host.hostname)
333
334        if not _successful(results['lid_open']):
335            raise error.TestFail('Lid stuck closed on %s' %
336                                     servo_host.hostname)
337