• 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
5"""This class defines the CrosHost Label class."""
6
7import logging
8import os
9import re
10
11import common
12
13from autotest_lib.client.bin import utils
14from autotest_lib.client.common_lib import global_config
15from autotest_lib.client.cros.audio import cras_utils
16from autotest_lib.client.cros.video import constants as video_test_constants
17from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
18from autotest_lib.server.hosts import base_label
19from autotest_lib.server.hosts import common_label
20from autotest_lib.server.hosts import servo_host
21from autotest_lib.site_utils import hwid_lib
22
23# pylint: disable=missing-docstring
24
25class BoardLabel(base_label.StringPrefixLabel):
26    """Determine the correct board label for the device."""
27
28    _NAME = ds_constants.BOARD_PREFIX.rstrip(':')
29
30    def generate_labels(self, host):
31        # We only want to apply the board labels once, which is when they get
32        # added to the AFE.  That way we don't have to worry about the board
33        # label switching on us if the wrong builds get put on the devices.
34        # crbug.com/624207 records one event of the board label switching
35        # unexpectedly on us.
36        for label in host._afe_host.labels:
37            if label.startswith(self._NAME + ':'):
38                return [label.split(':')[-1]]
39
40        # TODO(kevcheng): for now this will dup the code in CrosHost and a
41        # separate cl will refactor the get_board in CrosHost to just return the
42        # board without the BOARD_PREFIX and all the other callers will be
43        # updated to not need to clear it out and this code will be replaced to
44        # just call the host's get_board() method.
45        release_info = utils.parse_cmd_output('cat /etc/lsb-release',
46                                              run_method=host.run)
47        return [release_info['CHROMEOS_RELEASE_BOARD']]
48
49
50class LightSensorLabel(base_label.BaseLabel):
51    """Label indicating if a light sensor is detected."""
52
53    _NAME = 'lightsensor'
54    _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
55    _LIGHTSENSOR_FILES = [
56        "in_illuminance0_input",
57        "in_illuminance_input",
58        "in_illuminance0_raw",
59        "in_illuminance_raw",
60        "illuminance0_input",
61    ]
62
63    def exists(self, host):
64        search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
65            self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
66        # Run the search cmd following the symlinks. Stderr_tee is set to
67        # None as there can be a symlink loop, but the command will still
68        # execute correctly with a few messages printed to stderr.
69        result = host.run(search_cmd, stdout_tee=None, stderr_tee=None,
70                          ignore_status=True)
71
72        return result.exit_status == 0
73
74
75class BluetoothLabel(base_label.BaseLabel):
76    """Label indicating if bluetooth is detected."""
77
78    _NAME = 'bluetooth'
79
80    def exists(self, host):
81        result = host.run('test -d /sys/class/bluetooth/hci0',
82                          ignore_status=True)
83
84        return result.exit_status == 0
85
86
87class ECLabel(base_label.BaseLabel):
88    """Label to determine the type of EC on this host."""
89
90    _NAME = 'ec:cros'
91
92    def exists(self, host):
93        cmd = 'mosys ec info'
94        # The output should look like these, so that the last field should
95        # match our EC version scheme:
96        #
97        #   stm | stm32f100 | snow_v1.3.139-375eb9f
98        #   ti | Unknown-10de | peppy_v1.5.114-5d52788
99        #
100        # Non-Chrome OS ECs will look like these:
101        #
102        #   ENE | KB932 | 00BE107A00
103        #   ite | it8518 | 3.08
104        #
105        # And some systems don't have ECs at all (Lumpy, for example).
106        regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
107
108        ecinfo = host.run(command=cmd, ignore_status=True)
109        if ecinfo.exit_status == 0:
110            res = re.search(regexp, ecinfo.stdout)
111            if res:
112                logging.info("EC version is %s", res.groups()[0])
113                return True
114            logging.info("%s got: %s", cmd, ecinfo.stdout)
115            # Has an EC, but it's not a Chrome OS EC
116        logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
117        return False
118
119
120class AccelsLabel(base_label.BaseLabel):
121    """Determine the type of accelerometers on this host."""
122
123    _NAME = 'accel:cros-ec'
124
125    def exists(self, host):
126        # Check to make sure we have ectool
127        rv = host.run('which ectool', ignore_status=True)
128        if rv.exit_status:
129            logging.info("No ectool cmd found; assuming no EC accelerometers")
130            return False
131
132        # Check that the EC supports the motionsense command
133        rv = host.run('ectool motionsense', ignore_status=True)
134        if rv.exit_status:
135            logging.info("EC does not support motionsense command; "
136                         "assuming no EC accelerometers")
137            return False
138
139        # Check that EC motion sensors are active
140        active = host.run('ectool motionsense active').stdout.split('\n')
141        if active[0] == "0":
142            logging.info("Motion sense inactive; assuming no EC accelerometers")
143            return False
144
145        logging.info("EC accelerometers found")
146        return True
147
148
149class ChameleonLabel(base_label.BaseLabel):
150    """Determine if a Chameleon is connected to this host."""
151
152    _NAME = 'chameleon'
153
154    def exists(self, host):
155        return host._chameleon_host is not None
156
157
158class ChameleonConnectionLabel(base_label.StringPrefixLabel):
159    """Return the Chameleon connection label."""
160
161    _NAME = 'chameleon'
162
163    def exists(self, host):
164        return host._chameleon_host is not None
165
166
167    def generate_labels(self, host):
168        return [host.chameleon.get_label()]
169
170
171class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
172    """Return the Chameleon peripherals labels.
173
174    The 'chameleon:bt_hid' label is applied if the bluetooth
175    classic hid device, i.e, RN-42 emulation kit, is detected.
176
177    Any peripherals plugged into the chameleon board would be
178    detected and applied proper labels in this class.
179    """
180
181    _NAME = 'chameleon'
182
183    def exists(self, host):
184        return host._chameleon_host is not None
185
186
187    def generate_labels(self, host):
188        bt_hid_device = host.chameleon.get_bluetooh_hid_mouse()
189        return ['bt_hid'] if bt_hid_device.CheckSerialConnection() else []
190
191
192class AudioLoopbackDongleLabel(base_label.BaseLabel):
193    """Return the label if an audio loopback dongle is plugged in."""
194
195    _NAME = 'audio_loopback_dongle'
196
197    def exists(self, host):
198        nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
199                              ignore_status=True).stdout
200        if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
201            cras_utils.node_type_is_plugged('MIC', nodes_info)):
202                return True
203        return False
204
205
206class PowerSupplyLabel(base_label.StringPrefixLabel):
207    """
208    Return the label describing the power supply type.
209
210    Labels representing this host's power supply.
211         * `power:battery` when the device has a battery intended for
212                extended use
213         * `power:AC_primary` when the device has a battery not intended
214                for extended use (for moving the machine, etc)
215         * `power:AC_only` when the device has no battery at all.
216    """
217
218    _NAME = 'power'
219
220    def __init__(self):
221        self.psu_cmd_result = None
222
223
224    def exists(self, host):
225        self.psu_cmd_result = host.run(command='mosys psu type',
226                                       ignore_status=True)
227        return self.psu_cmd_result.stdout.strip() != 'unknown'
228
229
230    def generate_labels(self, host):
231        if self.psu_cmd_result.exit_status:
232            # The psu command for mosys is not included for all platforms. The
233            # assumption is that the device will have a battery if the command
234            # is not found.
235            return ['battery']
236        return [self.psu_cmd_result.stdout.strip()]
237
238
239class StorageLabel(base_label.StringPrefixLabel):
240    """
241    Return the label describing the storage type.
242
243    Determine if the internal device is SCSI or dw_mmc device.
244    Then check that it is SSD or HDD or eMMC or something else.
245
246    Labels representing this host's internal device type:
247             * `storage:ssd` when internal device is solid state drive
248             * `storage:hdd` when internal device is hard disk drive
249             * `storage:mmc` when internal device is mmc drive
250             * None          When internal device is something else or
251                             when we are unable to determine the type
252    """
253
254    _NAME = 'storage'
255
256    def __init__(self):
257        self.type_str = ''
258
259
260    def exists(self, host):
261        # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
262        rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
263                                '. /usr/share/misc/chromeos-common.sh;',
264                                'load_base_vars;',
265                                'get_fixed_dst_drive'])
266        rootdev = host.run(command=rootdev_cmd, ignore_status=True)
267        if rootdev.exit_status:
268            logging.info("Fail to run %s", rootdev_cmd)
269            return False
270        rootdev_str = rootdev.stdout.strip()
271
272        if not rootdev_str:
273            return False
274
275        rootdev_base = os.path.basename(rootdev_str)
276
277        mmc_pattern = '/dev/mmcblk[0-9]'
278        if re.match(mmc_pattern, rootdev_str):
279            # Use type to determine if the internal device is eMMC or somthing
280            # else. We can assume that MMC is always an internal device.
281            type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
282            type = host.run(command=type_cmd, ignore_status=True)
283            if type.exit_status:
284                logging.info("Fail to run %s", type_cmd)
285                return False
286            type_str = type.stdout.strip()
287
288            if type_str == 'MMC':
289                self.type_str = 'mmc'
290                return True
291
292        scsi_pattern = '/dev/sd[a-z]+'
293        if re.match(scsi_pattern, rootdev.stdout):
294            # Read symlink for /sys/block/sd* to determine if the internal
295            # device is connected via ata or usb.
296            link_cmd = 'readlink /sys/block/%s' % rootdev_base
297            link = host.run(command=link_cmd, ignore_status=True)
298            if link.exit_status:
299                logging.info("Fail to run %s", link_cmd)
300                return False
301            link_str = link.stdout.strip()
302            if 'usb' in link_str:
303                return False
304
305            # Read rotation to determine if the internal device is ssd or hdd.
306            rotate_cmd = str('cat /sys/block/%s/queue/rotational'
307                              % rootdev_base)
308            rotate = host.run(command=rotate_cmd, ignore_status=True)
309            if rotate.exit_status:
310                logging.info("Fail to run %s", rotate_cmd)
311                return False
312            rotate_str = rotate.stdout.strip()
313
314            rotate_dict = {'0':'ssd', '1':'hdd'}
315            self.type_str = rotate_dict.get(rotate_str)
316            return True
317
318        # All other internal device / error case will always fall here
319        return False
320
321
322    def generate_labels(self, host):
323        return [self.type_str]
324
325
326class ServoLabel(base_label.BaseLabel):
327    """Label to apply if a servo is present."""
328
329    _NAME = 'servo'
330
331    def exists(self, host):
332        """
333        Check if the servo label should apply to the host or not.
334
335        @returns True if a servo host is detected, False otherwise.
336        """
337        servo_args, _ = servo_host._get_standard_servo_args(host)
338        servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
339        return (servo_host_hostname is not None
340                and servo_host.servo_host_is_up(servo_host_hostname))
341
342
343class VideoLabel(base_label.StringLabel):
344    """Labels detailing video capabilities."""
345
346    # List gathered from
347    # https://chromium.googlesource.com/chromiumos/
348    # platform2/+/master/avtest_label_detect/main.c#19
349    _NAME = [
350        'hw_jpeg_acc_dec',
351        'hw_video_acc_h264',
352        'hw_video_acc_vp8',
353        'hw_video_acc_vp9',
354        'hw_video_acc_enc_h264',
355        'hw_video_acc_enc_vp8',
356        'webcam',
357    ]
358
359    def generate_labels(self, host):
360        result = host.run('/usr/local/bin/avtest_label_detect',
361                          ignore_status=True).stdout
362        return re.findall('^Detected label: (\w+)$', result, re.M)
363
364
365class CTSArchLabel(base_label.StringLabel):
366    """Labels to determine CTS abi."""
367
368    _NAME = ['cts_abi_arm', 'cts_abi_x86']
369
370    def _get_cts_abis(self, host):
371        """Return supported CTS ABIs.
372
373        @return List of supported CTS bundle ABIs.
374        """
375        cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
376        return cts_abis.get(host.get_cpu_arch(), [])
377
378
379    def generate_labels(self, host):
380        return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
381
382
383class ArcLabel(base_label.BaseLabel):
384    """Label indicates if host has ARC support."""
385
386    _NAME = 'arc'
387
388    @base_label.forever_exists_decorate
389    def exists(self, host):
390        return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
391                             ignore_status=True).exit_status
392
393
394class VideoGlitchLabel(base_label.BaseLabel):
395    """Label indicates if host supports video glitch detection tests."""
396
397    _NAME = 'video_glitch_detection'
398
399    def exists(self, host):
400        board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
401
402        return board in video_test_constants.SUPPORTED_BOARDS
403
404
405class InternalDisplayLabel(base_label.StringLabel):
406    """Label that determines if the device has an internal display."""
407
408    _NAME = 'internal_display'
409
410    def generate_labels(self, host):
411        from autotest_lib.client.cros.graphics import graphics_utils
412        from autotest_lib.client.common_lib import utils as common_utils
413
414        def __system_output(cmd):
415            return host.run(cmd).stdout
416
417        def __read_file(remote_path):
418            return host.run('cat %s' % remote_path).stdout
419
420        # Hijack the necessary client functions so that we can take advantage
421        # of the client lib here.
422        # FIXME: find a less hacky way than this
423        original_system_output = utils.system_output
424        original_read_file = common_utils.read_file
425        utils.system_output = __system_output
426        common_utils.read_file = __read_file
427        try:
428            return ([self._NAME]
429                    if graphics_utils.has_internal_display()
430                    else [])
431        finally:
432            utils.system_output = original_system_output
433            common_utils.read_file = original_read_file
434
435
436class LucidSleepLabel(base_label.BaseLabel):
437    """Label that determines if device has support for lucid sleep."""
438
439    # TODO(kevcheng): See if we can determine if this label is applicable a
440    # better way (crbug.com/592146).
441    _NAME = 'lucidsleep'
442    LUCID_SLEEP_BOARDS = ['samus', 'lulu']
443
444    def exists(self, host):
445        board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
446        return board in self.LUCID_SLEEP_BOARDS
447
448
449class HWIDLabel(base_label.StringLabel):
450    """Return all the labels generated from the hwid."""
451
452    # We leave out _NAME because hwid_lib will generate everything for us.
453
454    def __init__(self):
455        # Grab the key file needed to access the hwid service.
456        self.key_file = global_config.global_config.get_config_value(
457                'CROS', 'HWID_KEY', type=str)
458
459
460    def generate_labels(self, host):
461        hwid_labels = []
462        hwid = host.run_output('crossystem hwid').strip()
463        hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
464                                                self.key_file).get('labels', [])
465
466        for hwid_info in hwid_info_list:
467            # If it's a prefix, we'll have:
468            # {'name': prefix_label, 'value': postfix_label} and create
469            # 'prefix_label:postfix_label'; otherwise it'll just be
470            # {'name': label} which should just be 'label'.
471            value = hwid_info.get('value', '')
472            name = hwid_info.get('name', '')
473            # There should always be a name but just in case there is not.
474            if name:
475                hwid_labels.append(name if not value else
476                                   '%s:%s' % (name, value))
477        return hwid_labels
478
479
480    def get_all_labels(self):
481        """We need to try all labels as a prefix and as standalone.
482
483        We don't know for sure which labels are prefix labels and which are
484        standalone so we try all of them as both.
485        """
486        all_hwid_labels = []
487        try:
488            all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
489                    self.key_file)
490        except IOError:
491            logging.error('Can not open key file: %s', self.key_file)
492        except hwid_lib.HwIdException as e:
493            logging.error('hwid service: %s', e)
494        return all_hwid_labels, all_hwid_labels
495
496
497CROS_LABELS = [
498    AccelsLabel(),
499    ArcLabel(),
500    AudioLoopbackDongleLabel(),
501    BluetoothLabel(),
502    BoardLabel(),
503    ChameleonConnectionLabel(),
504    ChameleonLabel(),
505    ChameleonPeripheralsLabel(),
506    common_label.OSLabel(),
507    CTSArchLabel(),
508    ECLabel(),
509    HWIDLabel(),
510    InternalDisplayLabel(),
511    LightSensorLabel(),
512    LucidSleepLabel(),
513    PowerSupplyLabel(),
514    ServoLabel(),
515    StorageLabel(),
516    VideoGlitchLabel(),
517    VideoLabel(),
518]
519