blob: 919bf232b8cffaf0b124a1f1eb44edf9ac1eb5bd [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 Shapiro05dd3222017-09-22 10:42:33 -060055 # Board is used as the primary scheduling criteria for all autotest
56 # runs. Pre-unified builds, the board and build names matched.
57 #
58 # With unified builds, this is now the name of the builder
59 # and no longer the name of the board that's being targeted for testing.
60 #
61 # Until cbuildbot is migrated to begin using the model label for
62 # scheduling, we need compatibility with the board label so that it
63 # will work with unified builds in the meantime.
64 lsb_output = _parse_lsb_output(host)
65 if lsb_output.unibuild:
66 cmd = 'mosys platform model'
67 result = host.run(command=cmd, ignore_status=True)
68 return [result.stdout.strip()]
69 else:
70 return [lsb_output.board]
Kevin Chenga8455302016-08-31 20:54:41 +000071
72
C Shapirob05c00b2017-07-18 15:06:49 -060073class ModelLabel(base_label.StringPrefixLabel):
74 """Determine the correct model label for the device."""
75
76 _NAME = ds_constants.MODEL_LABEL
77
78 def generate_labels(self, host):
79 # Return the existing label if set to defend against any bad image
80 # pushes to the host. See comment in BoardLabel for more details.
81 for label in host._afe_host.labels:
82 if label.startswith(self._NAME + ':'):
83 return [label.split(':')[-1]]
84
C Shapiro05dd3222017-09-22 10:42:33 -060085 cmd = 'mosys platform model'
C Shapirob05c00b2017-07-18 15:06:49 -060086 result = host.run(command=cmd, ignore_status=True)
87 if result.exit_status == 0:
C Shapiro05dd3222017-09-22 10:42:33 -060088 return [result.stdout.strip()]
C Shapirob05c00b2017-07-18 15:06:49 -060089 else:
C Shapiro05dd3222017-09-22 10:42:33 -060090 # We need some sort of backwards compatibility for boards that
91 # are not yet supported with mosys and unified builds.
92 # This is necessary so that we can begin changing cbuildbot to take
93 # advantage of the model/board label differentiations for
94 # scheduling, while still retaining backwards compatibility.
95 return [_parse_lsb_output(host).board]
C Shapirob05c00b2017-07-18 15:06:49 -060096
97
Kevin Chenga2619dc2016-03-28 11:42:08 -070098class LightSensorLabel(base_label.BaseLabel):
99 """Label indicating if a light sensor is detected."""
100
101 _NAME = 'lightsensor'
102 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
103 _LIGHTSENSOR_FILES = [
104 "in_illuminance0_input",
105 "in_illuminance_input",
106 "in_illuminance0_raw",
107 "in_illuminance_raw",
108 "illuminance0_input",
109 ]
110
111 def exists(self, host):
112 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
113 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
114 # Run the search cmd following the symlinks. Stderr_tee is set to
115 # None as there can be a symlink loop, but the command will still
116 # execute correctly with a few messages printed to stderr.
117 result = host.run(search_cmd, stdout_tee=None, stderr_tee=None,
118 ignore_status=True)
119
120 return result.exit_status == 0
121
122
123class BluetoothLabel(base_label.BaseLabel):
124 """Label indicating if bluetooth is detected."""
125
126 _NAME = 'bluetooth'
127
128 def exists(self, host):
129 result = host.run('test -d /sys/class/bluetooth/hci0',
130 ignore_status=True)
131
132 return result.exit_status == 0
133
134
135class ECLabel(base_label.BaseLabel):
136 """Label to determine the type of EC on this host."""
137
138 _NAME = 'ec:cros'
139
140 def exists(self, host):
141 cmd = 'mosys ec info'
142 # The output should look like these, so that the last field should
143 # match our EC version scheme:
144 #
145 # stm | stm32f100 | snow_v1.3.139-375eb9f
146 # ti | Unknown-10de | peppy_v1.5.114-5d52788
147 #
148 # Non-Chrome OS ECs will look like these:
149 #
150 # ENE | KB932 | 00BE107A00
151 # ite | it8518 | 3.08
152 #
153 # And some systems don't have ECs at all (Lumpy, for example).
154 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
155
156 ecinfo = host.run(command=cmd, ignore_status=True)
157 if ecinfo.exit_status == 0:
158 res = re.search(regexp, ecinfo.stdout)
159 if res:
160 logging.info("EC version is %s", res.groups()[0])
161 return True
162 logging.info("%s got: %s", cmd, ecinfo.stdout)
163 # Has an EC, but it's not a Chrome OS EC
164 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
165 return False
166
167
168class AccelsLabel(base_label.BaseLabel):
169 """Determine the type of accelerometers on this host."""
170
171 _NAME = 'accel:cros-ec'
172
173 def exists(self, host):
174 # Check to make sure we have ectool
175 rv = host.run('which ectool', ignore_status=True)
176 if rv.exit_status:
177 logging.info("No ectool cmd found; assuming no EC accelerometers")
178 return False
179
180 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700181 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700182 if rv.exit_status:
183 logging.info("EC does not support motionsense command; "
184 "assuming no EC accelerometers")
185 return False
186
187 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700188 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700189 if active[0] == "0":
190 logging.info("Motion sense inactive; assuming no EC accelerometers")
191 return False
192
193 logging.info("EC accelerometers found")
194 return True
195
196
197class ChameleonLabel(base_label.BaseLabel):
198 """Determine if a Chameleon is connected to this host."""
199
200 _NAME = 'chameleon'
201
202 def exists(self, host):
203 return host._chameleon_host is not None
204
205
206class ChameleonConnectionLabel(base_label.StringPrefixLabel):
207 """Return the Chameleon connection label."""
208
209 _NAME = 'chameleon'
210
211 def exists(self, host):
212 return host._chameleon_host is not None
213
Joseph Hwangeac44312016-08-31 12:08:38 +0800214
Kevin Chenga2619dc2016-03-28 11:42:08 -0700215 def generate_labels(self, host):
216 return [host.chameleon.get_label()]
217
218
Joseph Hwangeac44312016-08-31 12:08:38 +0800219class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
220 """Return the Chameleon peripherals labels.
221
222 The 'chameleon:bt_hid' label is applied if the bluetooth
223 classic hid device, i.e, RN-42 emulation kit, is detected.
224
225 Any peripherals plugged into the chameleon board would be
226 detected and applied proper labels in this class.
227 """
228
229 _NAME = 'chameleon'
230
231 def exists(self, host):
232 return host._chameleon_host is not None
233
234
235 def generate_labels(self, host):
236 bt_hid_device = host.chameleon.get_bluetooh_hid_mouse()
237 return ['bt_hid'] if bt_hid_device.CheckSerialConnection() else []
238
239
Kevin Chenga2619dc2016-03-28 11:42:08 -0700240class AudioLoopbackDongleLabel(base_label.BaseLabel):
241 """Return the label if an audio loopback dongle is plugged in."""
242
243 _NAME = 'audio_loopback_dongle'
244
245 def exists(self, host):
246 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
247 ignore_status=True).stdout
248 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
249 cras_utils.node_type_is_plugged('MIC', nodes_info)):
250 return True
251 return False
252
253
254class PowerSupplyLabel(base_label.StringPrefixLabel):
255 """
256 Return the label describing the power supply type.
257
258 Labels representing this host's power supply.
259 * `power:battery` when the device has a battery intended for
260 extended use
261 * `power:AC_primary` when the device has a battery not intended
262 for extended use (for moving the machine, etc)
263 * `power:AC_only` when the device has no battery at all.
264 """
265
266 _NAME = 'power'
267
268 def __init__(self):
269 self.psu_cmd_result = None
270
271
272 def exists(self, host):
273 self.psu_cmd_result = host.run(command='mosys psu type',
274 ignore_status=True)
275 return self.psu_cmd_result.stdout.strip() != 'unknown'
276
277
278 def generate_labels(self, host):
279 if self.psu_cmd_result.exit_status:
280 # The psu command for mosys is not included for all platforms. The
281 # assumption is that the device will have a battery if the command
282 # is not found.
283 return ['battery']
284 return [self.psu_cmd_result.stdout.strip()]
285
286
287class StorageLabel(base_label.StringPrefixLabel):
288 """
289 Return the label describing the storage type.
290
291 Determine if the internal device is SCSI or dw_mmc device.
292 Then check that it is SSD or HDD or eMMC or something else.
293
294 Labels representing this host's internal device type:
295 * `storage:ssd` when internal device is solid state drive
296 * `storage:hdd` when internal device is hard disk drive
297 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700298 * `storage:nvme` when internal device is NVMe drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700299 * None When internal device is something else or
300 when we are unable to determine the type
301 """
302
303 _NAME = 'storage'
304
305 def __init__(self):
306 self.type_str = ''
307
308
309 def exists(self, host):
310 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
311 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
312 '. /usr/share/misc/chromeos-common.sh;',
313 'load_base_vars;',
314 'get_fixed_dst_drive'])
315 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
316 if rootdev.exit_status:
317 logging.info("Fail to run %s", rootdev_cmd)
318 return False
319 rootdev_str = rootdev.stdout.strip()
320
321 if not rootdev_str:
322 return False
323
324 rootdev_base = os.path.basename(rootdev_str)
325
326 mmc_pattern = '/dev/mmcblk[0-9]'
327 if re.match(mmc_pattern, rootdev_str):
328 # Use type to determine if the internal device is eMMC or somthing
329 # else. We can assume that MMC is always an internal device.
330 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
331 type = host.run(command=type_cmd, ignore_status=True)
332 if type.exit_status:
333 logging.info("Fail to run %s", type_cmd)
334 return False
335 type_str = type.stdout.strip()
336
337 if type_str == 'MMC':
338 self.type_str = 'mmc'
339 return True
340
341 scsi_pattern = '/dev/sd[a-z]+'
342 if re.match(scsi_pattern, rootdev.stdout):
343 # Read symlink for /sys/block/sd* to determine if the internal
344 # device is connected via ata or usb.
345 link_cmd = 'readlink /sys/block/%s' % rootdev_base
346 link = host.run(command=link_cmd, ignore_status=True)
347 if link.exit_status:
348 logging.info("Fail to run %s", link_cmd)
349 return False
350 link_str = link.stdout.strip()
351 if 'usb' in link_str:
352 return False
353
354 # Read rotation to determine if the internal device is ssd or hdd.
355 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
356 % rootdev_base)
357 rotate = host.run(command=rotate_cmd, ignore_status=True)
358 if rotate.exit_status:
359 logging.info("Fail to run %s", rotate_cmd)
360 return False
361 rotate_str = rotate.stdout.strip()
362
363 rotate_dict = {'0':'ssd', '1':'hdd'}
364 self.type_str = rotate_dict.get(rotate_str)
365 return True
366
Gwendal Grignou327fec62017-07-26 15:25:43 -0700367 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
368 if re.match(nvme_pattern, rootdev_str):
369 self.type_str = 'nmve'
370 return True
371
Kevin Chenga2619dc2016-03-28 11:42:08 -0700372 # All other internal device / error case will always fall here
373 return False
374
375
376 def generate_labels(self, host):
377 return [self.type_str]
378
379
380class ServoLabel(base_label.BaseLabel):
381 """Label to apply if a servo is present."""
382
383 _NAME = 'servo'
384
385 def exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700386 """
387 Check if the servo label should apply to the host or not.
388
389 @returns True if a servo host is detected, False otherwise.
390 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700391 servo_host_hostname = None
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700392 servo_args, _ = servo_host._get_standard_servo_args(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700393 if servo_args:
394 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700395 return (servo_host_hostname is not None
396 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700397
398
399class VideoLabel(base_label.StringLabel):
400 """Labels detailing video capabilities."""
401
402 # List gathered from
403 # https://chromium.googlesource.com/chromiumos/
404 # platform2/+/master/avtest_label_detect/main.c#19
Hirokazu Hondad6cfe922017-10-03 13:07:37 +0900405 # TODO(hiroh): '4k_video' won't be used. It will be removed in the future.
Kevin Chenga2619dc2016-03-28 11:42:08 -0700406 _NAME = [
407 'hw_jpeg_acc_dec',
408 'hw_video_acc_h264',
409 'hw_video_acc_vp8',
410 'hw_video_acc_vp9',
411 'hw_video_acc_enc_h264',
412 'hw_video_acc_enc_vp8',
413 'webcam',
Hirokazu Honda57dbf002017-09-21 16:05:06 +0900414 '4k_video',
Hirokazu Hondad6cfe922017-10-03 13:07:37 +0900415 '4k_video_h264',
416 '4k_video_vp8',
417 '4k_video_vp9',
Kevin Chenga2619dc2016-03-28 11:42:08 -0700418 ]
419
420 def generate_labels(self, host):
421 result = host.run('/usr/local/bin/avtest_label_detect',
422 ignore_status=True).stdout
423 return re.findall('^Detected label: (\w+)$', result, re.M)
424
425
Rohit Makasana5a153502016-06-13 15:50:09 -0700426class CTSArchLabel(base_label.StringLabel):
427 """Labels to determine CTS abi."""
428
429 _NAME = ['cts_abi_arm', 'cts_abi_x86']
430
431 def _get_cts_abis(self, host):
432 """Return supported CTS ABIs.
433
434 @return List of supported CTS bundle ABIs.
435 """
436 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
437 return cts_abis.get(host.get_cpu_arch(), [])
438
439
440 def generate_labels(self, host):
441 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
442
443
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700444class ArcLabel(base_label.BaseLabel):
445 """Label indicates if host has ARC support."""
446
447 _NAME = 'arc'
448
Kevin Chengeee38e02016-08-30 16:13:23 -0700449 @base_label.forever_exists_decorate
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700450 def exists(self, host):
451 return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
452 ignore_status=True).exit_status
453
454
Kevin Chenga2619dc2016-03-28 11:42:08 -0700455class VideoGlitchLabel(base_label.BaseLabel):
456 """Label indicates if host supports video glitch detection tests."""
457
458 _NAME = 'video_glitch_detection'
459
460 def exists(self, host):
461 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
462
463 return board in video_test_constants.SUPPORTED_BOARDS
464
465
Kevin Chenga2619dc2016-03-28 11:42:08 -0700466class InternalDisplayLabel(base_label.StringLabel):
467 """Label that determines if the device has an internal display."""
468
469 _NAME = 'internal_display'
470
471 def generate_labels(self, host):
472 from autotest_lib.client.cros.graphics import graphics_utils
473 from autotest_lib.client.common_lib import utils as common_utils
474
475 def __system_output(cmd):
476 return host.run(cmd).stdout
477
478 def __read_file(remote_path):
479 return host.run('cat %s' % remote_path).stdout
480
481 # Hijack the necessary client functions so that we can take advantage
482 # of the client lib here.
483 # FIXME: find a less hacky way than this
484 original_system_output = utils.system_output
485 original_read_file = common_utils.read_file
486 utils.system_output = __system_output
487 common_utils.read_file = __read_file
488 try:
489 return ([self._NAME]
490 if graphics_utils.has_internal_display()
491 else [])
492 finally:
493 utils.system_output = original_system_output
494 common_utils.read_file = original_read_file
495
496
497class LucidSleepLabel(base_label.BaseLabel):
498 """Label that determines if device has support for lucid sleep."""
499
500 # TODO(kevcheng): See if we can determine if this label is applicable a
501 # better way (crbug.com/592146).
502 _NAME = 'lucidsleep'
503 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
504
505 def exists(self, host):
506 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
507 return board in self.LUCID_SLEEP_BOARDS
508
509
Kevin Cheng80ad5732016-03-31 16:01:56 -0700510class HWIDLabel(base_label.StringLabel):
511 """Return all the labels generated from the hwid."""
512
513 # We leave out _NAME because hwid_lib will generate everything for us.
514
515 def __init__(self):
516 # Grab the key file needed to access the hwid service.
517 self.key_file = global_config.global_config.get_config_value(
518 'CROS', 'HWID_KEY', type=str)
519
520
521 def generate_labels(self, host):
522 hwid_labels = []
523 hwid = host.run_output('crossystem hwid').strip()
524 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
525 self.key_file).get('labels', [])
526
527 for hwid_info in hwid_info_list:
528 # If it's a prefix, we'll have:
529 # {'name': prefix_label, 'value': postfix_label} and create
530 # 'prefix_label:postfix_label'; otherwise it'll just be
531 # {'name': label} which should just be 'label'.
532 value = hwid_info.get('value', '')
533 name = hwid_info.get('name', '')
534 # There should always be a name but just in case there is not.
535 if name:
536 hwid_labels.append(name if not value else
537 '%s:%s' % (name, value))
538 return hwid_labels
539
540
541 def get_all_labels(self):
542 """We need to try all labels as a prefix and as standalone.
543
544 We don't know for sure which labels are prefix labels and which are
545 standalone so we try all of them as both.
546 """
547 all_hwid_labels = []
548 try:
549 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
550 self.key_file)
551 except IOError:
552 logging.error('Can not open key file: %s', self.key_file)
553 except hwid_lib.HwIdException as e:
554 logging.error('hwid service: %s', e)
555 return all_hwid_labels, all_hwid_labels
556
557
Kevin Chenga2619dc2016-03-28 11:42:08 -0700558CROS_LABELS = [
559 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700560 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700561 AudioLoopbackDongleLabel(),
562 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000563 BoardLabel(),
C Shapiro05dd3222017-09-22 10:42:33 -0600564 ModelLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700565 ChameleonConnectionLabel(),
566 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800567 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700568 common_label.OSLabel(),
Rohit Makasana5a153502016-06-13 15:50:09 -0700569 CTSArchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700570 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700571 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700572 InternalDisplayLabel(),
573 LightSensorLabel(),
574 LucidSleepLabel(),
575 PowerSupplyLabel(),
576 ServoLabel(),
577 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700578 VideoGlitchLabel(),
579 VideoLabel(),
580]