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