blob: 7cf31a20bd7f65ae0a1608b876deae516b496a58 [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
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -070027# fallback values if we can't contact the HWID server
28HWID_LABELS_FALLBACK = ['sku', 'phase', 'touchscreen', 'touchpad', 'variant', 'stylus']
29
30
C Shapiro05dd3222017-09-22 10:42:33 -060031def _parse_lsb_output(host):
Allen Lia0c7afc2019-02-26 15:50:06 -080032 """Parses the LSB output and returns key data points for labeling.
C Shapiro05dd3222017-09-22 10:42:33 -060033
Allen Lia0c7afc2019-02-26 15:50:06 -080034 @param host: Host that the command will be executed against
35 @returns: LsbOutput with the result of parsing the /etc/lsb-release output
36 """
37 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
38 run_method=host.run)
C Shapiro05dd3222017-09-22 10:42:33 -060039
Allen Lia0c7afc2019-02-26 15:50:06 -080040 unibuild = release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1'
41 return LsbOutput(unibuild, release_info['CHROMEOS_RELEASE_BOARD'])
C Shapiro05dd3222017-09-22 10:42:33 -060042
Kevin Chenga2619dc2016-03-28 11:42:08 -070043
Kevin Chenga8455302016-08-31 20:54:41 +000044class BoardLabel(base_label.StringPrefixLabel):
45 """Determine the correct board label for the device."""
46
47 _NAME = ds_constants.BOARD_PREFIX.rstrip(':')
48
49 def generate_labels(self, host):
50 # We only want to apply the board labels once, which is when they get
51 # added to the AFE. That way we don't have to worry about the board
52 # label switching on us if the wrong builds get put on the devices.
53 # crbug.com/624207 records one event of the board label switching
54 # unexpectedly on us.
Allen Lia0c7afc2019-02-26 15:50:06 -080055 board = host.host_info_store.get().board
56 if board:
57 return [board]
Kevin Chenga8455302016-08-31 20:54:41 +000058 for label in host._afe_host.labels:
59 if label.startswith(self._NAME + ':'):
60 return [label.split(':')[-1]]
61
C Shapiro10970222017-10-24 08:55:55 -060062 return [_parse_lsb_output(host).board]
Kevin Chenga8455302016-08-31 20:54:41 +000063
64
C Shapirob05c00b2017-07-18 15:06:49 -060065class ModelLabel(base_label.StringPrefixLabel):
66 """Determine the correct model label for the device."""
67
68 _NAME = ds_constants.MODEL_LABEL
69
70 def generate_labels(self, host):
C Shapirod7ba4a72018-01-16 17:04:35 -070071 # Based on the issue explained in BoardLabel, return the existing
72 # label if it has already been set once.
Allen Lia0c7afc2019-02-26 15:50:06 -080073 model = host.host_info_store.get().model
74 if model:
75 return [model]
C Shapirod7ba4a72018-01-16 17:04:35 -070076 for label in host._afe_host.labels:
77 if label.startswith(self._NAME + ':'):
78 return [label.split(':')[-1]]
C Shapirob05c00b2017-07-18 15:06:49 -060079
C Shapiro32700032017-11-03 12:46:55 -060080 lsb_output = _parse_lsb_output(host)
81 model = None
82
83 if lsb_output.unibuild:
C Shapiro26fb1012017-12-14 16:38:03 -070084 test_label_cmd = 'cros_config / test-label'
85 result = host.run(command=test_label_cmd, ignore_status=True)
C Shapiro32700032017-11-03 12:46:55 -060086 if result.exit_status == 0:
87 model = result.stdout.strip()
C Shapiro26fb1012017-12-14 16:38:03 -070088 if not model:
89 mosys_cmd = 'mosys platform model'
90 result = host.run(command=mosys_cmd, ignore_status=True)
91 if result.exit_status == 0:
92 model = result.stdout.strip()
C Shapiro32700032017-11-03 12:46:55 -060093
94 # We need some sort of backwards compatibility for boards that
95 # are not yet supported with mosys and unified builds.
96 # This is necessary so that we can begin changing cbuildbot to take
97 # advantage of the model/board label differentiations for
98 # scheduling, while still retaining backwards compatibility.
99 return [model or lsb_output.board]
C Shapirob05c00b2017-07-18 15:06:49 -0600100
101
C Shapirobe0ff8d2019-06-14 10:41:43 -0600102class DeviceSkuLabel(base_label.StringPrefixLabel):
103 """Determine the correct device_sku label for the device."""
104
105 _NAME = ds_constants.DEVICE_SKU_LABEL
106
107 def generate_labels(self, host):
108 device_sku = host.host_info_store.get().device_sku
109 if device_sku:
110 return [device_sku]
111
112 mosys_cmd = 'mosys platform sku'
113 result = host.run(command=mosys_cmd, ignore_status=True)
114 if result.exit_status == 0:
115 return [result.stdout.strip()]
116
117 return []
118
119
Ned Nguyene0a619d2019-07-01 15:50:23 -0600120class BrandCodeLabel(base_label.StringPrefixLabel):
121 """Determine the correct brand_code (aka RLZ-code) for the device."""
122
123 _NAME = ds_constants.BRAND_CODE_LABEL
124
125 def generate_labels(self, host):
126 brand_code = host.host_info_store.get().brand_code
127 if brand_code:
128 return [brand_code]
129
Greg Edelston7cea0c42019-11-26 15:17:22 -0700130 cros_config_cmd = 'cros_config / brand-code'
131 result = host.run(command=cros_config_cmd, ignore_status=True)
Ned Nguyene0a619d2019-07-01 15:50:23 -0600132 if result.exit_status == 0:
133 return [result.stdout.strip()]
134
135 return []
136
137
Kevin Chenga2619dc2016-03-28 11:42:08 -0700138class BluetoothLabel(base_label.BaseLabel):
139 """Label indicating if bluetooth is detected."""
140
141 _NAME = 'bluetooth'
142
143 def exists(self, host):
C Shapirobcd9c862019-05-22 17:42:08 -0600144 # Based on crbug.com/966219, the label is flipping sometimes.
145 # Potentially this is caused by testing itself.
146 # Making this label permanently sticky.
147 info = host.host_info_store.get()
148 for label in info.labels:
149 if label.startswith(self._NAME):
150 return True
151
Kevin Chenga2619dc2016-03-28 11:42:08 -0700152 result = host.run('test -d /sys/class/bluetooth/hci0',
153 ignore_status=True)
154
155 return result.exit_status == 0
156
157
158class ECLabel(base_label.BaseLabel):
159 """Label to determine the type of EC on this host."""
160
161 _NAME = 'ec:cros'
162
163 def exists(self, host):
164 cmd = 'mosys ec info'
165 # The output should look like these, so that the last field should
166 # match our EC version scheme:
167 #
168 # stm | stm32f100 | snow_v1.3.139-375eb9f
169 # ti | Unknown-10de | peppy_v1.5.114-5d52788
170 #
171 # Non-Chrome OS ECs will look like these:
172 #
173 # ENE | KB932 | 00BE107A00
174 # ite | it8518 | 3.08
175 #
176 # And some systems don't have ECs at all (Lumpy, for example).
177 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
178
179 ecinfo = host.run(command=cmd, ignore_status=True)
180 if ecinfo.exit_status == 0:
181 res = re.search(regexp, ecinfo.stdout)
182 if res:
183 logging.info("EC version is %s", res.groups()[0])
184 return True
185 logging.info("%s got: %s", cmd, ecinfo.stdout)
186 # Has an EC, but it's not a Chrome OS EC
187 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
188 return False
189
190
Mary Ruthven935ebad2018-06-13 16:13:20 -0700191class Cr50Label(base_label.StringPrefixLabel):
Mary Ruthven6c462642019-09-17 19:13:36 -0700192 """Label indicating the cr50 image type."""
Mary Ruthven935ebad2018-06-13 16:13:20 -0700193
194 _NAME = 'cr50'
195
196 def __init__(self):
197 self.ver = None
198
Mary Ruthven935ebad2018-06-13 16:13:20 -0700199 def exists(self, host):
200 # Make sure the gsctool version command runs ok
201 self.ver = host.run('gsctool -a -f', ignore_status=True)
202 return self.ver.exit_status == 0
203
Mary Ruthven6c462642019-09-17 19:13:36 -0700204 def _get_version(self, region):
205 """Get the version number of the given region"""
206 return re.search(region + ' (\d+\.\d+\.\d+)', self.ver.stdout).group(1)
Mary Ruthven935ebad2018-06-13 16:13:20 -0700207
208 def generate_labels(self, host):
209 # Check the major version to determine prePVT vs PVT
Mary Ruthven6c462642019-09-17 19:13:36 -0700210 version = self._get_version('RW')
211 major_version = int(version.split('.')[1])
Mary Ruthven935ebad2018-06-13 16:13:20 -0700212 # PVT images have a odd major version prePVT have even
Mary Ruthven6c462642019-09-17 19:13:36 -0700213 return ['pvt' if (major_version % 2) else 'prepvt']
214
215
216class Cr50RWKeyidLabel(Cr50Label):
217 """Label indicating the cr50 RW version."""
218 _REGION = 'RW'
219 _NAME = 'cr50-rw-keyid'
220
221 def _get_keyid_info(self, region):
222 """Get the keyid of the given region."""
223 match = re.search('keyids:.*%s (\S+)' % region, self.ver.stdout)
224 keyid = match.group(1).rstrip(',')
225 is_prod = int(keyid, 16) & (1 << 2)
226 return [keyid, 'prod' if is_prod else 'dev']
227
228 def generate_labels(self, host):
229 """Get the key type."""
230 return self._get_keyid_info(self._REGION)
231
232
233class Cr50ROKeyidLabel(Cr50RWKeyidLabel):
234 """Label indicating the RO key type."""
235 _REGION = 'RO'
236 _NAME = 'cr50-ro-keyid'
237
238
239class Cr50RWVersionLabel(Cr50Label):
240 """Label indicating the cr50 RW version."""
241 _REGION = 'RW'
242 _NAME = 'cr50-rw-version'
243
244 def generate_labels(self, host):
245 """Get the version and key type"""
246 return [self._get_version(self._REGION)]
247
248
249class Cr50ROVersionLabel(Cr50RWVersionLabel):
250 """Label indicating the RO version."""
251 _REGION = 'RO'
252 _NAME = 'cr50-ro-version'
Mary Ruthven935ebad2018-06-13 16:13:20 -0700253
254
Kevin Chenga2619dc2016-03-28 11:42:08 -0700255class AccelsLabel(base_label.BaseLabel):
256 """Determine the type of accelerometers on this host."""
257
258 _NAME = 'accel:cros-ec'
259
260 def exists(self, host):
261 # Check to make sure we have ectool
262 rv = host.run('which ectool', ignore_status=True)
263 if rv.exit_status:
264 logging.info("No ectool cmd found; assuming no EC accelerometers")
265 return False
266
267 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700268 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700269 if rv.exit_status:
270 logging.info("EC does not support motionsense command; "
271 "assuming no EC accelerometers")
272 return False
273
274 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700275 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700276 if active[0] == "0":
277 logging.info("Motion sense inactive; assuming no EC accelerometers")
278 return False
279
280 logging.info("EC accelerometers found")
281 return True
282
283
284class ChameleonLabel(base_label.BaseLabel):
285 """Determine if a Chameleon is connected to this host."""
286
287 _NAME = 'chameleon'
288
289 def exists(self, host):
Xixuan Wu7afb54f2019-09-17 11:45:20 -0700290 # See crbug.com/1004500#2 for details.
291 # https://chromium.googlesource.com/chromiumos/third_party/autotest/+
292 # /refs/heads/master/server/hosts/cros_host.py#335 shows that
293 # _chameleon_host_list is not reliable.
294 has_chameleon = len(host.chameleon_list) > 0
Gregory Nisbetb2f6d792019-09-11 14:30:47 -0700295 # TODO(crbug.com/995900) -- debug why chameleon label is flipping
296 try:
297 logging.info("has_chameleon %s", has_chameleon)
Xixuan Wu6833caf2019-09-17 10:37:17 -0700298 logging.info("chameleon_host_list %s",
299 getattr(host, "_chameleon_host_list", "NO_ATTRIBUTE"))
300 logging.info("chameleon_list %s",
301 getattr(host, "chameleon_list", "NO_ATTRIBUTE"))
302 logging.info("multi_chameleon %s",
303 getattr(host, "multi_chameleon", "NO_ATTRIBUTE"))
Gregory Nisbetb2f6d792019-09-11 14:30:47 -0700304 except:
305 pass
306 return has_chameleon
307
Kevin Chenga2619dc2016-03-28 11:42:08 -0700308
309
310class ChameleonConnectionLabel(base_label.StringPrefixLabel):
311 """Return the Chameleon connection label."""
312
313 _NAME = 'chameleon'
314
315 def exists(self, host):
howardchung83e55272019-08-08 14:08:05 +0800316 return len(host._chameleon_host_list) > 0
Kevin Chenga2619dc2016-03-28 11:42:08 -0700317
Joseph Hwangeac44312016-08-31 12:08:38 +0800318
Kevin Chenga2619dc2016-03-28 11:42:08 -0700319 def generate_labels(self, host):
howardchungc1380ad2019-09-23 15:40:45 +0800320 return [chameleon.get_label() for chameleon in host.chameleon_list]
Kevin Chenga2619dc2016-03-28 11:42:08 -0700321
322
Joseph Hwangeac44312016-08-31 12:08:38 +0800323class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
324 """Return the Chameleon peripherals labels.
325
326 The 'chameleon:bt_hid' label is applied if the bluetooth
327 classic hid device, i.e, RN-42 emulation kit, is detected.
328
329 Any peripherals plugged into the chameleon board would be
330 detected and applied proper labels in this class.
331 """
332
333 _NAME = 'chameleon'
334
335 def exists(self, host):
howardchung83e55272019-08-08 14:08:05 +0800336 return len(host._chameleon_host_list) > 0
Joseph Hwangeac44312016-08-31 12:08:38 +0800337
338
339 def generate_labels(self, host):
howardchung83e55272019-08-08 14:08:05 +0800340 labels_list = []
341
342 for chameleon, chameleon_host in \
343 zip(host.chameleon_list, host._chameleon_host_list):
344 labels = []
345 try:
346 bt_hid_device = chameleon.get_bluetooth_hid_mouse()
347 if bt_hid_device.CheckSerialConnection():
348 labels.append('bt_hid')
349 except:
350 logging.error('Error with initializing bt_hid_mouse on '
351 'chameleon %s', chameleon_host.hostname)
352
353 try:
354 ble_hid_device = chameleon.get_ble_mouse()
355 if ble_hid_device.CheckSerialConnection():
356 labels.append('bt_ble_hid')
357 except:
358 logging.error('Error with initializing ble_hid_mouse on '
359 'chameleon %s', chameleon_host.hostname)
360
361 try:
362 bt_a2dp_sink = chameleon.get_bluetooth_a2dp_sink()
363 if bt_a2dp_sink.CheckSerialConnection():
364 labels.append('bt_a2dp_sink')
365 except:
366 logging.error('Error with initializing bt_a2dp_sink on '
367 'chameleon %s', chameleon_host.hostname)
368
Joseph Hwang89e779c2019-12-24 16:05:56 +0800369 try:
370 bt_base_device = chameleon.get_bluetooth_base()
371 if bt_base_device.IsDetected():
372 labels.append('bt_base')
373 except:
374 logging.error('Error in detecting bt_base on '
375 'chameleon %s', chameleon_host.hostname)
376
howardchung83e55272019-08-08 14:08:05 +0800377 if labels != []:
378 labels.append('bt_peer')
379
380 if host.multi_chameleon:
381 labels_list.append(labels)
382 else:
383 labels_list.extend(labels)
384
385
386 logging.info('Bluetooth labels are %s', labels_list)
387 return labels_list
Shijin Abrahamff61ac32019-05-20 12:35:44 -0700388
389
Joseph Hwangeac44312016-08-31 12:08:38 +0800390
391
Kevin Chenga2619dc2016-03-28 11:42:08 -0700392class AudioLoopbackDongleLabel(base_label.BaseLabel):
393 """Return the label if an audio loopback dongle is plugged in."""
394
395 _NAME = 'audio_loopback_dongle'
396
397 def exists(self, host):
Gregory Nisbete280ea22019-08-16 17:50:03 -0700398 # Based on crbug.com/991285, AudioLoopbackDongle sometimes flips.
399 # Ensure that AudioLoopbackDongle.exists returns True
400 # forever, after it returns True *once*.
401 if self._cached_exists(host):
402 # If the current state is True, return it, don't run the command on
403 # the DUT and potentially flip the state.
404 return True
405 # If the current state is not True, run the command on
406 # the DUT. The new state will be set to whatever the command
407 # produces.
408 return self._host_run_exists(host)
409
410 def _cached_exists(self, host):
411 """Get the state of AudioLoopbackDongle in the data store"""
412 info = host.host_info_store.get()
413 for label in info.labels:
414 if label.startswith(self._NAME):
415 return True
416 return False
417
418 def _host_run_exists(self, host):
419 """Detect presence of audio_loopback_dongle by physically
420 running a command on the DUT."""
Kevin Chenga2619dc2016-03-28 11:42:08 -0700421 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
422 ignore_status=True).stdout
423 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
424 cras_utils.node_type_is_plugged('MIC', nodes_info)):
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800425 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700426 return False
427
428
429class PowerSupplyLabel(base_label.StringPrefixLabel):
430 """
431 Return the label describing the power supply type.
432
433 Labels representing this host's power supply.
434 * `power:battery` when the device has a battery intended for
435 extended use
436 * `power:AC_primary` when the device has a battery not intended
437 for extended use (for moving the machine, etc)
438 * `power:AC_only` when the device has no battery at all.
439 """
440
441 _NAME = 'power'
442
443 def __init__(self):
444 self.psu_cmd_result = None
445
446
447 def exists(self, host):
448 self.psu_cmd_result = host.run(command='mosys psu type',
449 ignore_status=True)
450 return self.psu_cmd_result.stdout.strip() != 'unknown'
451
452
453 def generate_labels(self, host):
454 if self.psu_cmd_result.exit_status:
455 # The psu command for mosys is not included for all platforms. The
456 # assumption is that the device will have a battery if the command
457 # is not found.
458 return ['battery']
459 return [self.psu_cmd_result.stdout.strip()]
460
461
462class StorageLabel(base_label.StringPrefixLabel):
463 """
464 Return the label describing the storage type.
465
466 Determine if the internal device is SCSI or dw_mmc device.
467 Then check that it is SSD or HDD or eMMC or something else.
468
469 Labels representing this host's internal device type:
470 * `storage:ssd` when internal device is solid state drive
471 * `storage:hdd` when internal device is hard disk drive
472 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700473 * `storage:nvme` when internal device is NVMe drive
Alexis Savery570e7fb2018-06-26 10:48:15 -0700474 * `storage:ufs` when internal device is ufs drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700475 * None When internal device is something else or
476 when we are unable to determine the type
477 """
478
479 _NAME = 'storage'
480
481 def __init__(self):
482 self.type_str = ''
483
484
485 def exists(self, host):
486 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
487 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
488 '. /usr/share/misc/chromeos-common.sh;',
489 'load_base_vars;',
490 'get_fixed_dst_drive'])
491 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
492 if rootdev.exit_status:
493 logging.info("Fail to run %s", rootdev_cmd)
494 return False
495 rootdev_str = rootdev.stdout.strip()
496
497 if not rootdev_str:
498 return False
499
500 rootdev_base = os.path.basename(rootdev_str)
501
502 mmc_pattern = '/dev/mmcblk[0-9]'
503 if re.match(mmc_pattern, rootdev_str):
504 # Use type to determine if the internal device is eMMC or somthing
505 # else. We can assume that MMC is always an internal device.
506 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
507 type = host.run(command=type_cmd, ignore_status=True)
508 if type.exit_status:
509 logging.info("Fail to run %s", type_cmd)
510 return False
511 type_str = type.stdout.strip()
512
513 if type_str == 'MMC':
514 self.type_str = 'mmc'
515 return True
516
517 scsi_pattern = '/dev/sd[a-z]+'
518 if re.match(scsi_pattern, rootdev.stdout):
519 # Read symlink for /sys/block/sd* to determine if the internal
520 # device is connected via ata or usb.
521 link_cmd = 'readlink /sys/block/%s' % rootdev_base
522 link = host.run(command=link_cmd, ignore_status=True)
523 if link.exit_status:
524 logging.info("Fail to run %s", link_cmd)
525 return False
526 link_str = link.stdout.strip()
527 if 'usb' in link_str:
528 return False
Alexis Savery570e7fb2018-06-26 10:48:15 -0700529 elif 'ufs' in link_str:
530 self.type_str = 'ufs'
531 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700532
533 # Read rotation to determine if the internal device is ssd or hdd.
534 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
535 % rootdev_base)
536 rotate = host.run(command=rotate_cmd, ignore_status=True)
537 if rotate.exit_status:
538 logging.info("Fail to run %s", rotate_cmd)
539 return False
540 rotate_str = rotate.stdout.strip()
541
542 rotate_dict = {'0':'ssd', '1':'hdd'}
543 self.type_str = rotate_dict.get(rotate_str)
544 return True
545
Gwendal Grignou327fec62017-07-26 15:25:43 -0700546 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
547 if re.match(nvme_pattern, rootdev_str):
Gwendal Grignou3660c192017-12-06 10:11:23 -0800548 self.type_str = 'nvme'
Gwendal Grignou327fec62017-07-26 15:25:43 -0700549 return True
550
Kevin Chenga2619dc2016-03-28 11:42:08 -0700551 # All other internal device / error case will always fall here
552 return False
553
Kevin Chenga2619dc2016-03-28 11:42:08 -0700554 def generate_labels(self, host):
555 return [self.type_str]
556
557
558class ServoLabel(base_label.BaseLabel):
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800559 """
560 Label servo is applying if a servo is present.
561 Label servo_state present always.
562 """
Kevin Chenga2619dc2016-03-28 11:42:08 -0700563
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800564 _NAME_OLD = 'servo'
565 _NAME = 'servo_state'
566 _NAME_WORKING = 'servo_state:WORKING'
567 _NAME_BROKEN = 'servo_state:BROKEN'
568
569 def get(self, host):
570 if self.exists(host):
571 return [self._NAME_OLD, self._NAME_WORKING]
572 return [self._NAME_BROKEN]
573
574 def get_all_labels(self):
575 return set([self._NAME]), set([self._NAME_OLD])
Kevin Chenga2619dc2016-03-28 11:42:08 -0700576
577 def exists(self, host):
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700578 # Based on crbug.com/995900, Servo sometimes flips.
579 # Ensure that ServoLabel.exists returns True
580 # forever, after it returns True *once*.
581 if self._cached_exists(host):
582 # If the current state is True, return it, don't run the command on
583 # the DUT and potentially flip the state.
584 return True
585 # If the current state is not True, run the command on
586 # the DUT. The new state will be set to whatever the command
587 # produces.
588 return self._host_run_exists(host)
589
590 def _cached_exists(self, host):
591 """Get the state of Servo in the data store"""
592 info = host.host_info_store.get()
593 for label in info.labels:
594 if label.startswith(self._NAME):
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800595 if label.startswith(self._NAME_WORKING):
596 return True
597 elif label.startswith(self._NAME_OLD):
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700598 return True
599 return False
600
601 def _host_run_exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700602 """
603 Check if the servo label should apply to the host or not.
604
605 @returns True if a servo host is detected, False otherwise.
606 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700607 servo_host_hostname = None
Prathmesh Prabhu37ae79b2018-09-12 10:37:44 -0700608 servo_args = servo_host.get_servo_args_for_host(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700609 if servo_args:
610 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700611 return (servo_host_hostname is not None
612 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700613
614
Ilja H. Friedel50290642017-12-01 19:39:53 -0800615class ArcLabel(base_label.BaseLabel):
616 """Label indicates if host has ARC support."""
617
618 _NAME = 'arc'
619
620 @base_label.forever_exists_decorate
621 def exists(self, host):
622 return 0 == host.run(
623 'grep CHROMEOS_ARC_VERSION /etc/lsb-release',
624 ignore_status=True).exit_status
625
626
627class CtsArchLabel(base_label.StringLabel):
628 """Labels to determine the abi of the CTS bundle (arm or x86 only)."""
Rohit Makasana5a153502016-06-13 15:50:09 -0700629
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700630 _NAME = ['cts_abi_arm', 'cts_abi_x86', 'cts_cpu_arm', 'cts_cpu_x86']
Rohit Makasana5a153502016-06-13 15:50:09 -0700631
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700632 def _get_cts_abis(self, arch):
Rohit Makasana5a153502016-06-13 15:50:09 -0700633 """Return supported CTS ABIs.
634
635 @return List of supported CTS bundle ABIs.
636 """
637 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700638 return cts_abis.get(arch, [])
639
640 def _get_cts_cpus(self, arch):
641 """Return supported CTS native CPUs.
642
643 This is needed for CTS_Instant scheduling.
644 @return List of supported CTS native CPUs.
645 """
646 cts_cpus = {'x86_64': ['x86'], 'arm': ['arm']}
647 return cts_cpus.get(arch, [])
Rohit Makasana5a153502016-06-13 15:50:09 -0700648
Rohit Makasana5a153502016-06-13 15:50:09 -0700649 def generate_labels(self, host):
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700650 cpu_arch = host.get_cpu_arch()
651 abi_labels = ['cts_abi_' + abi for abi in self._get_cts_abis(cpu_arch)]
652 cpu_labels = ['cts_cpu_' + cpu for cpu in self._get_cts_cpus(cpu_arch)]
653 return abi_labels + cpu_labels
Rohit Makasana5a153502016-06-13 15:50:09 -0700654
655
Kevin Chenga2619dc2016-03-28 11:42:08 -0700656class VideoGlitchLabel(base_label.BaseLabel):
657 """Label indicates if host supports video glitch detection tests."""
658
659 _NAME = 'video_glitch_detection'
660
661 def exists(self, host):
662 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
663
664 return board in video_test_constants.SUPPORTED_BOARDS
665
666
Kevin Chenga2619dc2016-03-28 11:42:08 -0700667class InternalDisplayLabel(base_label.StringLabel):
668 """Label that determines if the device has an internal display."""
669
670 _NAME = 'internal_display'
671
672 def generate_labels(self, host):
673 from autotest_lib.client.cros.graphics import graphics_utils
674 from autotest_lib.client.common_lib import utils as common_utils
675
676 def __system_output(cmd):
677 return host.run(cmd).stdout
678
679 def __read_file(remote_path):
680 return host.run('cat %s' % remote_path).stdout
681
682 # Hijack the necessary client functions so that we can take advantage
683 # of the client lib here.
684 # FIXME: find a less hacky way than this
685 original_system_output = utils.system_output
686 original_read_file = common_utils.read_file
687 utils.system_output = __system_output
688 common_utils.read_file = __read_file
689 try:
690 return ([self._NAME]
691 if graphics_utils.has_internal_display()
692 else [])
693 finally:
694 utils.system_output = original_system_output
695 common_utils.read_file = original_read_file
696
697
698class LucidSleepLabel(base_label.BaseLabel):
699 """Label that determines if device has support for lucid sleep."""
700
701 # TODO(kevcheng): See if we can determine if this label is applicable a
702 # better way (crbug.com/592146).
703 _NAME = 'lucidsleep'
RaviChandra Sadinenic06b00e2018-11-03 09:56:11 -0700704 LUCID_SLEEP_BOARDS = ['nocturne', 'poppy']
Kevin Chenga2619dc2016-03-28 11:42:08 -0700705
706 def exists(self, host):
707 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
708 return board in self.LUCID_SLEEP_BOARDS
709
710
Xixuan Wu78569d02019-09-15 16:08:25 -0700711def _parse_hwid_labels(hwid_info_list):
712 if len(hwid_info_list) == 0:
713 return hwid_info_list
714
715 res = []
716 # See crbug.com/997816#c7 for details of two potential formats of returns
717 # from HWID server.
718 if isinstance(hwid_info_list[0], dict):
719 # Format of hwid_info:
720 # [{u'name': u'sku', u'value': u'xxx'}, ..., ]
721 for hwid_info in hwid_info_list:
722 value = hwid_info.get('value', '')
723 name = hwid_info.get('name', '')
724 # There should always be a name but just in case there is not.
725 if name:
726 new_label = name if not value else '%s:%s' % (name, value)
727 res.append(new_label)
728 else:
729 # Format of hwid_info:
730 # [<DUTLabel name: 'sku' value: u'xxx'>, ..., ]
731 for hwid_info in hwid_info_list:
732 new_label = str(hwid_info)
733 logging.info('processing hwid label: %s', new_label)
734 res.append(new_label)
735
736 return res
737
738
Kevin Cheng80ad5732016-03-31 16:01:56 -0700739class HWIDLabel(base_label.StringLabel):
740 """Return all the labels generated from the hwid."""
741
742 # We leave out _NAME because hwid_lib will generate everything for us.
743
744 def __init__(self):
745 # Grab the key file needed to access the hwid service.
746 self.key_file = global_config.global_config.get_config_value(
747 'CROS', 'HWID_KEY', type=str)
748
749
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700750 @staticmethod
751 def _merge_hwid_label_lists(new, old):
752 """merge a list of old and new values for hwid_labels.
753 preferring new values if available
754
755 @returns: list of labels"""
756 # TODO(gregorynisbet): what is the appropriate way to merge
757 # old and new information?
758 retained = set(x for x in old)
759 for label in new:
760 key, sep, value = label.partition(':')
761 # If we have a key-value key such as variant:aaa,
762 # then we remove all the old labels with the same key.
763 if sep:
764 retained = set(x for x in retained if (not x.startswith(key + ':')))
765 return list(sorted(retained.union(new)))
766
767
768 def _hwid_label_names(self):
769 """get the labels that hwid_lib controls.
770
771 @returns: hwid_labels
772 """
773 all_hwid_labels, _ = self.get_all_labels()
774 # If and only if get_all_labels was unsuccessful,
775 # it will return a falsey value.
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700776 out = all_hwid_labels or HWID_LABELS_FALLBACK
777
778 # TODO(gregorynisbet): remove this
779 # TODO(crbug.com/999785)
780 if "sku" not in out:
781 logging.info("sku-less label names %s", out)
782
783 return out
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700784
785
786 def _old_label_values(self, host):
787 """get the hwid_lib labels on previous run
788
789 @returns: hwid_labels"""
790 out = []
791 info = host.host_info_store.get()
792 for hwid_label in self._hwid_label_names():
793 for label in info.labels:
794 # NOTE: we want *all* the labels starting
795 # with this prefix.
796 if label.startswith(hwid_label):
797 out.append(label)
798 return out
799
800
Kevin Cheng80ad5732016-03-31 16:01:56 -0700801 def generate_labels(self, host):
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700802 # use previous values as default
803 old_hwid_labels = self._old_label_values(host)
Xixuan Wue63f8352019-09-13 15:18:03 -0700804 logging.info("old_hwid_labels: %r", old_hwid_labels)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700805 hwid = host.run_output('crossystem hwid').strip()
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700806 hwid_info_list = []
807 try:
808 hwid_info_response = hwid_lib.get_hwid_info(
809 hwid=hwid,
810 info_type=hwid_lib.HWID_INFO_LABEL,
811 key_file=self.key_file,
812 )
Xixuan Wue63f8352019-09-13 15:18:03 -0700813 logging.info("hwid_info_response: %r", hwid_info_response)
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700814 hwid_info_list = hwid_info_response.get('labels', [])
815 except hwid_lib.HwIdException as e:
816 logging.info("HwIdException: %s", e)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700817
Xixuan Wu78569d02019-09-15 16:08:25 -0700818 new_hwid_labels = _parse_hwid_labels(hwid_info_list)
819 logging.info("new HWID labels: %r", new_hwid_labels)
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700820
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700821 return HWIDLabel._merge_hwid_label_lists(
822 old=old_hwid_labels,
Xixuan Wu78569d02019-09-15 16:08:25 -0700823 new=new_hwid_labels,
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700824 )
Kevin Cheng80ad5732016-03-31 16:01:56 -0700825
826
827 def get_all_labels(self):
828 """We need to try all labels as a prefix and as standalone.
829
830 We don't know for sure which labels are prefix labels and which are
831 standalone so we try all of them as both.
832 """
833 all_hwid_labels = []
834 try:
835 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
836 self.key_file)
837 except IOError:
838 logging.error('Can not open key file: %s', self.key_file)
839 except hwid_lib.HwIdException as e:
840 logging.error('hwid service: %s', e)
841 return all_hwid_labels, all_hwid_labels
842
843
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800844class DetachableBaseLabel(base_label.BaseLabel):
845 """Label indicating if device has detachable keyboard."""
846
847 _NAME = 'detachablebase'
848
849 def exists(self, host):
850 return host.run('which hammerd', ignore_status=True).exit_status == 0
851
852
Tom Hughese9552342018-12-18 14:29:25 -0800853class FingerprintLabel(base_label.BaseLabel):
854 """Label indicating whether device has fingerprint sensor."""
855
856 _NAME = 'fingerprint'
857
858 def exists(self, host):
859 return host.run('test -c /dev/cros_fp',
860 ignore_status=True).exit_status == 0
861
862
Garry Wang17a829e2019-03-20 12:03:18 -0700863class ReferenceDesignLabel(base_label.StringPrefixLabel):
864 """Determine the correct reference design label for the device. """
865
866 _NAME = 'reference_design'
867
868 def __init__(self):
869 self.response = None
870
871 def exists(self, host):
872 self.response = host.run('mosys platform family', ignore_status=True)
873 return self.response.exit_status == 0
874
875 def generate_labels(self, host):
876 if self.exists(host):
877 return [self.response.stdout.strip()]
878
879
Kevin Chenga2619dc2016-03-28 11:42:08 -0700880CROS_LABELS = [
881 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700882 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700883 AudioLoopbackDongleLabel(),
884 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000885 BoardLabel(),
C Shapiro05dd3222017-09-22 10:42:33 -0600886 ModelLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700887 ChameleonConnectionLabel(),
888 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800889 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700890 common_label.OSLabel(),
Mary Ruthven935ebad2018-06-13 16:13:20 -0700891 Cr50Label(),
Mary Ruthven6c462642019-09-17 19:13:36 -0700892 Cr50ROKeyidLabel(),
893 Cr50RWKeyidLabel(),
894 Cr50ROVersionLabel(),
895 Cr50RWVersionLabel(),
Ilja H. Friedel50290642017-12-01 19:39:53 -0800896 CtsArchLabel(),
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800897 DetachableBaseLabel(),
C Shapiro8315f5f2019-06-19 15:53:29 -0600898 DeviceSkuLabel(),
Ned Nguyene0a619d2019-07-01 15:50:23 -0600899 BrandCodeLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700900 ECLabel(),
Tom Hughese9552342018-12-18 14:29:25 -0800901 FingerprintLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700902 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700903 InternalDisplayLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700904 LucidSleepLabel(),
905 PowerSupplyLabel(),
Garry Wang17a829e2019-03-20 12:03:18 -0700906 ReferenceDesignLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700907 ServoLabel(),
908 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700909 VideoGlitchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700910]
Garry Wange4b6d6e2019-06-17 17:08:46 -0700911
912LABSTATION_LABELS = [
913 BoardLabel(),
914 ModelLabel(),
915 common_label.OSLabel(),
Garry Wang1d0598a2019-09-06 16:31:56 -0700916 PowerSupplyLabel(),
Garry Wange4b6d6e2019-06-17 17:08:46 -0700917]