• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import logging
6import time
7
8import common
9from autotest_lib.client.common_lib import hosts
10from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
11from autotest_lib.server.hosts import repair_utils
12
13
14class _UpdateVerifier(hosts.Verifier):
15    """
16    Verifier to trigger a servo host update, if necessary.
17
18    The operation doesn't wait for the update to complete and is
19    considered a success whether or not the servo is currently
20    up-to-date.
21    """
22
23    def verify(self, host):
24        # First, only run this verifier if the host is in the physical lab.
25        # Secondly, skip if the test is being run by test_that, because subnet
26        # restrictions can cause the update to fail.
27        if host.is_in_lab() and host.job and host.job.in_lab:
28            host.update_image(wait_for_update=False)
29
30    @property
31    def description(self):
32        return 'servo host software is up-to-date'
33
34
35class _ConfigVerifier(hosts.Verifier):
36    """
37    Base verifier for the servo config file verifiers.
38    """
39
40    CONFIG_FILE = '/var/lib/servod/config'
41    ATTR = ''
42
43    @staticmethod
44    def _get_config_val(host, config_file, attr):
45        """
46        Get the `attr` for `host` from `config_file`.
47
48        @param host         Host to be checked for `config_file`.
49        @param config_file  Path to the config file to be tested.
50        @param attr         Attribute to get from config file.
51
52        @return The attr val as set in the config file, or `None` if
53                the file was absent.
54        """
55        getboard = ('CONFIG=%s ; [ -f $CONFIG ] && '
56                    '. $CONFIG && echo $%s' % (config_file, attr))
57        attr_val = host.run(getboard, ignore_status=True).stdout
58        return attr_val.strip('\n') if attr_val else None
59
60    @staticmethod
61    def _validate_attr(host, val, expected_val, attr, config_file):
62        """
63        Check that the attr setting is valid for the host.
64
65        This presupposes that a valid config file was found.  Raise an
66        execption if:
67          * There was no attr setting from the file (i.e. the setting
68            is an empty string), or
69          * The attr setting is valid, the attr is known,
70            and the setting doesn't match the DUT.
71
72        @param host         Host to be checked for `config_file`.
73        @param val          Value to be tested.
74        @param expected_val Expected value.
75        @param attr         Attribute we're validating.
76        @param config_file  Path to the config file to be tested.
77        """
78        if not val:
79            raise hosts.AutoservVerifyError(
80                    'config file %s exists, but %s '
81                    'is not set' % (attr, config_file))
82        if expected_val is not None and val != expected_val:
83            raise hosts.AutoservVerifyError(
84                    '%s is %s; it should be %s' % (attr, val, expected_val))
85
86
87    def _get_config(self, host):
88        """
89        Return the config file to check.
90
91        @param host     Host object.
92
93        @return The config file to check.
94        """
95        return '%s_%d' % (self.CONFIG_FILE, host.servo_port)
96
97    @property
98    def description(self):
99        return 'servo %s setting is correct' % self.ATTR
100
101
102class _SerialConfigVerifier(_ConfigVerifier):
103    """
104    Verifier for the servo SERIAL configuration.
105    """
106
107    ATTR = 'SERIAL'
108
109    def verify(self, host):
110        """
111        Test whether the `host` has a `SERIAL` setting configured.
112
113        This tests the config file names used by the `servod` upstart
114        job for a valid setting of the `SERIAL` variable.  The following
115        conditions raise errors:
116          * The SERIAL setting doesn't match the DUT's entry in the AFE
117            database.
118          * There is no config file.
119        """
120        if not host.is_cros_host():
121            return
122        # Not all servo hosts will have a servo serial so don't verify if it's
123        # not set.
124        if host.servo_serial is None:
125            return
126        config = self._get_config(host)
127        serialval = self._get_config_val(host, config, self.ATTR)
128        if serialval is None:
129            raise hosts.AutoservVerifyError(
130                    'Servo serial is unconfigured; should be %s'
131                    % host.servo_serial
132            )
133
134        self._validate_attr(host, serialval, host.servo_serial, self.ATTR,
135                            config)
136
137
138
139class _BoardConfigVerifier(_ConfigVerifier):
140    """
141    Verifier for the servo BOARD configuration.
142    """
143
144    ATTR = 'BOARD'
145
146    def verify(self, host):
147        """
148        Test whether the `host` has a `BOARD` setting configured.
149
150        This tests the config file names used by the `servod` upstart
151        job for a valid setting of the `BOARD` variable.  The following
152        conditions raise errors:
153          * A config file exists, but the content contains no setting
154            for BOARD.
155          * The BOARD setting doesn't match the DUT's entry in the AFE
156            database.
157          * There is no config file.
158        """
159        if not host.is_cros_host():
160            return
161        config = self._get_config(host)
162        boardval = self._get_config_val(host, config, self.ATTR)
163        if boardval is None:
164            msg = 'Servo board is unconfigured'
165            if host.servo_board is not None:
166                msg += '; should be %s' % host.servo_board
167            raise hosts.AutoservVerifyError(msg)
168
169        self._validate_attr(host, boardval, host.servo_board, self.ATTR,
170                            config)
171
172
173class _ServodJobVerifier(hosts.Verifier):
174    """
175    Verifier to check that the `servod` upstart job is running.
176    """
177
178    def verify(self, host):
179        if not host.is_cros_host():
180            return
181        status_cmd = 'status servod PORT=%d' % host.servo_port
182        job_status = host.run(status_cmd, ignore_status=True).stdout
183        if 'start/running' not in job_status:
184            raise hosts.AutoservVerifyError(
185                    'servod not running on %s port %d' %
186                    (host.hostname, host.servo_port))
187
188    @property
189    def description(self):
190        return 'servod upstart job is running'
191
192
193class _ServodConnectionVerifier(hosts.Verifier):
194    """
195    Verifier to check that we can connect to `servod`.
196
197    This tests the connection to the target servod service with a simple
198    method call.  As a side-effect, all servo signals are initialized to
199    default values.
200
201    N.B. Initializing servo signals is necessary because the power
202    button and lid switch verifiers both test against expected initial
203    values.
204    """
205
206    def verify(self, host):
207        host.connect_servo()
208
209    @property
210    def description(self):
211        return 'servod service is taking calls'
212
213
214class _PowerButtonVerifier(hosts.Verifier):
215    """
216    Verifier to check sanity of the `pwr_button` signal.
217
218    Tests that the `pwr_button` signal shows the power button has been
219    released.  When `pwr_button` is stuck at `press`, it commonly
220    indicates that the ribbon cable is disconnected.
221    """
222    # TODO (crbug.com/646593) - Remove list below once servo has been updated
223    # with a dummy pwr_button signal.
224    _BOARDS_WO_PWR_BUTTON = ['arkham', 'gale', 'mistral', 'storm', 'whirlwind']
225
226    def verify(self, host):
227        if host.servo_board in self._BOARDS_WO_PWR_BUTTON:
228            return
229        button = host.get_servo().get('pwr_button')
230        if button != 'release':
231            raise hosts.AutoservVerifyError(
232                    'Check ribbon cable: \'pwr_button\' is stuck')
233
234    @property
235    def description(self):
236        return 'pwr_button control is normal'
237
238
239class _LidVerifier(hosts.Verifier):
240    """
241    Verifier to check sanity of the `lid_open` signal.
242    """
243
244    def verify(self, host):
245        lid_open = host.get_servo().get('lid_open')
246        if lid_open != 'yes' and lid_open != 'not_applicable':
247            raise hosts.AutoservVerifyError(
248                    'Check lid switch: lid_open is %s' % lid_open)
249
250    @property
251    def description(self):
252        return 'lid_open control is normal'
253
254
255class _RestartServod(hosts.RepairAction):
256    """Restart `servod` with the proper BOARD setting."""
257
258    def repair(self, host):
259        if not host.is_cros_host():
260            raise hosts.AutoservRepairError(
261                    'Can\'t restart servod: not running '
262                    'embedded Chrome OS.',
263                    'servo_not_applicable_to_non_cros_host')
264        host.run('stop servod PORT=%d || true' % host.servo_port)
265        serial = 'SERIAL=%s' % host.servo_serial if host.servo_serial else ''
266        model = 'MODEL=%s' % host.servo_model if host.servo_model else ''
267        if host.servo_board:
268            host.run('start servod BOARD=%s %s PORT=%d %s' %
269                     (host.servo_board, model, host.servo_port, serial))
270        else:
271            # TODO(jrbarnette):  It remains to be seen whether
272            # this action is the right thing to do...
273            logging.warning('Board for DUT is unknown; starting '
274                            'servod assuming a pre-configured '
275                            'board.')
276            host.run('start servod PORT=%d %s' % (host.servo_port, serial))
277        # There's a lag between when `start servod` completes and when
278        # the _ServodConnectionVerifier trigger can actually succeed.
279        # The call to time.sleep() below gives time to make sure that
280        # the trigger won't fail after we return.
281        #
282        # The delay selection was based on empirical testing against
283        # servo V3 on a desktop:
284        #   + 10 seconds was usually too slow; 11 seconds was
285        #     usually fast enough.
286        #   + So, the 20 second delay is about double what we
287        #     expect to need.
288        time.sleep(20)
289
290
291    @property
292    def description(self):
293        return 'Start servod with the proper config settings.'
294
295
296class _ServoRebootRepair(repair_utils.RebootRepair):
297    """
298    Reboot repair action that also waits for an update.
299
300    This is the same as the standard `RebootRepair`, but for
301    a servo host, if there's a pending update, we wait for that
302    to complete before rebooting.  This should ensure that the
303    servo is up-to-date after reboot.
304    """
305
306    def repair(self, host):
307        if host.is_localhost() or not host.is_cros_host():
308            raise hosts.AutoservRepairError(
309                'Target servo is not a test lab servo',
310                'servo_not_applicable_to_host_outside_lab')
311        host.update_image(wait_for_update=True)
312        afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
313        dut_list = host.get_attached_duts(afe)
314        if len(dut_list) > 1:
315            raise hosts.AutoservRepairError(
316                    'Repairing labstation with > 1 host not supported.'
317                    ' See crbug.com/843358',
318                    'can_not_repair_labstation_with_multiple_hosts')
319        else:
320            super(_ServoRebootRepair, self).repair(host)
321
322    @property
323    def description(self):
324        return 'Wait for update, then reboot servo host.'
325
326
327class _DutRebootRepair(hosts.RepairAction):
328    """
329    Reboot DUT to recover some servo controls depending on EC console.
330
331    Some servo controls, like lid_open, requires communicating with DUT through
332    EC UART console. Failure of this kinds of controls can be recovered by
333    rebooting the DUT.
334    """
335
336    def repair(self, host):
337        host.get_servo().get_power_state_controller().reset()
338        # Get the lid_open value which requires EC console.
339        lid_open = host.get_servo().get('lid_open')
340        if lid_open != 'yes' and lid_open != 'not_applicable':
341            raise hosts.AutoservVerifyError(
342                    'Still fail to contact EC console after rebooting DUT')
343
344    @property
345    def description(self):
346        return 'Reset the DUT via servo'
347
348
349def create_servo_repair_strategy():
350    """
351    Return a `RepairStrategy` for a `ServoHost`.
352    """
353    config = ['brd_config', 'ser_config']
354    verify_dag = [
355        (repair_utils.SshVerifier,   'servo_ssh',   []),
356        (_UpdateVerifier,            'update',      ['servo_ssh']),
357        (_BoardConfigVerifier,       'brd_config',  ['servo_ssh']),
358        (_SerialConfigVerifier,      'ser_config',  ['servo_ssh']),
359        (_ServodJobVerifier,         'job',         config),
360        (_ServodConnectionVerifier,  'servod',      ['job']),
361        (_PowerButtonVerifier,       'pwr_button',  ['servod']),
362        (_LidVerifier,               'lid_open',    ['servod']),
363        # TODO(jrbarnette):  We want a verifier for whether there's
364        # a working USB stick plugged into the servo.  However,
365        # although we always want to log USB stick problems, we don't
366        # want to fail the servo because we don't want a missing USB
367        # stick to prevent, say, power cycling the DUT.
368        #
369        # So, it may be that the right fix is to put diagnosis into
370        # ServoInstallRepair rather than add a verifier.
371    ]
372
373    servod_deps = ['job', 'servod', 'pwr_button']
374    repair_actions = [
375        (repair_utils.RPMCycleRepair, 'rpm', [], ['servo_ssh']),
376        (_RestartServod, 'restart', ['servo_ssh'], config + servod_deps),
377        (_ServoRebootRepair, 'servo_reboot', ['servo_ssh'], servod_deps),
378        (_DutRebootRepair, 'dut_reboot', ['servod'], ['lid_open']),
379    ]
380    return hosts.RepairStrategy(verify_dag, repair_actions, 'servo')
381