blob: 186a2e138213873122aa70e9b64ce23fb4b79e2f [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)):
425 return True
426 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
554
555 def generate_labels(self, host):
556 return [self.type_str]
557
558
559class ServoLabel(base_label.BaseLabel):
560 """Label to apply if a servo is present."""
561
562 _NAME = 'servo'
563
564 def exists(self, host):
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700565 # Based on crbug.com/995900, Servo sometimes flips.
566 # Ensure that ServoLabel.exists returns True
567 # forever, after it returns True *once*.
568 if self._cached_exists(host):
569 # If the current state is True, return it, don't run the command on
570 # the DUT and potentially flip the state.
571 return True
572 # If the current state is not True, run the command on
573 # the DUT. The new state will be set to whatever the command
574 # produces.
575 return self._host_run_exists(host)
576
577 def _cached_exists(self, host):
578 """Get the state of Servo in the data store"""
579 info = host.host_info_store.get()
580 for label in info.labels:
581 if label.startswith(self._NAME):
582 return True
583 return False
584
585 def _host_run_exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700586 """
587 Check if the servo label should apply to the host or not.
588
589 @returns True if a servo host is detected, False otherwise.
590 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700591 servo_host_hostname = None
Prathmesh Prabhu37ae79b2018-09-12 10:37:44 -0700592 servo_args = servo_host.get_servo_args_for_host(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700593 if servo_args:
594 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700595 return (servo_host_hostname is not None
596 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700597
598
Ilja H. Friedel50290642017-12-01 19:39:53 -0800599class ArcLabel(base_label.BaseLabel):
600 """Label indicates if host has ARC support."""
601
602 _NAME = 'arc'
603
604 @base_label.forever_exists_decorate
605 def exists(self, host):
606 return 0 == host.run(
607 'grep CHROMEOS_ARC_VERSION /etc/lsb-release',
608 ignore_status=True).exit_status
609
610
611class CtsArchLabel(base_label.StringLabel):
612 """Labels to determine the abi of the CTS bundle (arm or x86 only)."""
Rohit Makasana5a153502016-06-13 15:50:09 -0700613
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700614 _NAME = ['cts_abi_arm', 'cts_abi_x86', 'cts_cpu_arm', 'cts_cpu_x86']
Rohit Makasana5a153502016-06-13 15:50:09 -0700615
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700616 def _get_cts_abis(self, arch):
Rohit Makasana5a153502016-06-13 15:50:09 -0700617 """Return supported CTS ABIs.
618
619 @return List of supported CTS bundle ABIs.
620 """
621 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700622 return cts_abis.get(arch, [])
623
624 def _get_cts_cpus(self, arch):
625 """Return supported CTS native CPUs.
626
627 This is needed for CTS_Instant scheduling.
628 @return List of supported CTS native CPUs.
629 """
630 cts_cpus = {'x86_64': ['x86'], 'arm': ['arm']}
631 return cts_cpus.get(arch, [])
Rohit Makasana5a153502016-06-13 15:50:09 -0700632
Rohit Makasana5a153502016-06-13 15:50:09 -0700633 def generate_labels(self, host):
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700634 cpu_arch = host.get_cpu_arch()
635 abi_labels = ['cts_abi_' + abi for abi in self._get_cts_abis(cpu_arch)]
636 cpu_labels = ['cts_cpu_' + cpu for cpu in self._get_cts_cpus(cpu_arch)]
637 return abi_labels + cpu_labels
Rohit Makasana5a153502016-06-13 15:50:09 -0700638
639
Kevin Chenga2619dc2016-03-28 11:42:08 -0700640class VideoGlitchLabel(base_label.BaseLabel):
641 """Label indicates if host supports video glitch detection tests."""
642
643 _NAME = 'video_glitch_detection'
644
645 def exists(self, host):
646 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
647
648 return board in video_test_constants.SUPPORTED_BOARDS
649
650
Kevin Chenga2619dc2016-03-28 11:42:08 -0700651class InternalDisplayLabel(base_label.StringLabel):
652 """Label that determines if the device has an internal display."""
653
654 _NAME = 'internal_display'
655
656 def generate_labels(self, host):
657 from autotest_lib.client.cros.graphics import graphics_utils
658 from autotest_lib.client.common_lib import utils as common_utils
659
660 def __system_output(cmd):
661 return host.run(cmd).stdout
662
663 def __read_file(remote_path):
664 return host.run('cat %s' % remote_path).stdout
665
666 # Hijack the necessary client functions so that we can take advantage
667 # of the client lib here.
668 # FIXME: find a less hacky way than this
669 original_system_output = utils.system_output
670 original_read_file = common_utils.read_file
671 utils.system_output = __system_output
672 common_utils.read_file = __read_file
673 try:
674 return ([self._NAME]
675 if graphics_utils.has_internal_display()
676 else [])
677 finally:
678 utils.system_output = original_system_output
679 common_utils.read_file = original_read_file
680
681
682class LucidSleepLabel(base_label.BaseLabel):
683 """Label that determines if device has support for lucid sleep."""
684
685 # TODO(kevcheng): See if we can determine if this label is applicable a
686 # better way (crbug.com/592146).
687 _NAME = 'lucidsleep'
RaviChandra Sadinenic06b00e2018-11-03 09:56:11 -0700688 LUCID_SLEEP_BOARDS = ['nocturne', 'poppy']
Kevin Chenga2619dc2016-03-28 11:42:08 -0700689
690 def exists(self, host):
691 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
692 return board in self.LUCID_SLEEP_BOARDS
693
694
Xixuan Wu78569d02019-09-15 16:08:25 -0700695def _parse_hwid_labels(hwid_info_list):
696 if len(hwid_info_list) == 0:
697 return hwid_info_list
698
699 res = []
700 # See crbug.com/997816#c7 for details of two potential formats of returns
701 # from HWID server.
702 if isinstance(hwid_info_list[0], dict):
703 # Format of hwid_info:
704 # [{u'name': u'sku', u'value': u'xxx'}, ..., ]
705 for hwid_info in hwid_info_list:
706 value = hwid_info.get('value', '')
707 name = hwid_info.get('name', '')
708 # There should always be a name but just in case there is not.
709 if name:
710 new_label = name if not value else '%s:%s' % (name, value)
711 res.append(new_label)
712 else:
713 # Format of hwid_info:
714 # [<DUTLabel name: 'sku' value: u'xxx'>, ..., ]
715 for hwid_info in hwid_info_list:
716 new_label = str(hwid_info)
717 logging.info('processing hwid label: %s', new_label)
718 res.append(new_label)
719
720 return res
721
722
Kevin Cheng80ad5732016-03-31 16:01:56 -0700723class HWIDLabel(base_label.StringLabel):
724 """Return all the labels generated from the hwid."""
725
726 # We leave out _NAME because hwid_lib will generate everything for us.
727
728 def __init__(self):
729 # Grab the key file needed to access the hwid service.
730 self.key_file = global_config.global_config.get_config_value(
731 'CROS', 'HWID_KEY', type=str)
732
733
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700734 @staticmethod
735 def _merge_hwid_label_lists(new, old):
736 """merge a list of old and new values for hwid_labels.
737 preferring new values if available
738
739 @returns: list of labels"""
740 # TODO(gregorynisbet): what is the appropriate way to merge
741 # old and new information?
742 retained = set(x for x in old)
743 for label in new:
744 key, sep, value = label.partition(':')
745 # If we have a key-value key such as variant:aaa,
746 # then we remove all the old labels with the same key.
747 if sep:
748 retained = set(x for x in retained if (not x.startswith(key + ':')))
749 return list(sorted(retained.union(new)))
750
751
752 def _hwid_label_names(self):
753 """get the labels that hwid_lib controls.
754
755 @returns: hwid_labels
756 """
757 all_hwid_labels, _ = self.get_all_labels()
758 # If and only if get_all_labels was unsuccessful,
759 # it will return a falsey value.
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700760 out = all_hwid_labels or HWID_LABELS_FALLBACK
761
762 # TODO(gregorynisbet): remove this
763 # TODO(crbug.com/999785)
764 if "sku" not in out:
765 logging.info("sku-less label names %s", out)
766
767 return out
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700768
769
770 def _old_label_values(self, host):
771 """get the hwid_lib labels on previous run
772
773 @returns: hwid_labels"""
774 out = []
775 info = host.host_info_store.get()
776 for hwid_label in self._hwid_label_names():
777 for label in info.labels:
778 # NOTE: we want *all* the labels starting
779 # with this prefix.
780 if label.startswith(hwid_label):
781 out.append(label)
782 return out
783
784
Kevin Cheng80ad5732016-03-31 16:01:56 -0700785 def generate_labels(self, host):
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700786 # use previous values as default
787 old_hwid_labels = self._old_label_values(host)
Xixuan Wue63f8352019-09-13 15:18:03 -0700788 logging.info("old_hwid_labels: %r", old_hwid_labels)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700789 hwid = host.run_output('crossystem hwid').strip()
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700790 hwid_info_list = []
791 try:
792 hwid_info_response = hwid_lib.get_hwid_info(
793 hwid=hwid,
794 info_type=hwid_lib.HWID_INFO_LABEL,
795 key_file=self.key_file,
796 )
Xixuan Wue63f8352019-09-13 15:18:03 -0700797 logging.info("hwid_info_response: %r", hwid_info_response)
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700798 hwid_info_list = hwid_info_response.get('labels', [])
799 except hwid_lib.HwIdException as e:
800 logging.info("HwIdException: %s", e)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700801
Xixuan Wu78569d02019-09-15 16:08:25 -0700802 new_hwid_labels = _parse_hwid_labels(hwid_info_list)
803 logging.info("new HWID labels: %r", new_hwid_labels)
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700804
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700805 return HWIDLabel._merge_hwid_label_lists(
806 old=old_hwid_labels,
Xixuan Wu78569d02019-09-15 16:08:25 -0700807 new=new_hwid_labels,
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700808 )
Kevin Cheng80ad5732016-03-31 16:01:56 -0700809
810
811 def get_all_labels(self):
812 """We need to try all labels as a prefix and as standalone.
813
814 We don't know for sure which labels are prefix labels and which are
815 standalone so we try all of them as both.
816 """
817 all_hwid_labels = []
818 try:
819 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
820 self.key_file)
821 except IOError:
822 logging.error('Can not open key file: %s', self.key_file)
823 except hwid_lib.HwIdException as e:
824 logging.error('hwid service: %s', e)
825 return all_hwid_labels, all_hwid_labels
826
827
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800828class DetachableBaseLabel(base_label.BaseLabel):
829 """Label indicating if device has detachable keyboard."""
830
831 _NAME = 'detachablebase'
832
833 def exists(self, host):
834 return host.run('which hammerd', ignore_status=True).exit_status == 0
835
836
Tom Hughese9552342018-12-18 14:29:25 -0800837class FingerprintLabel(base_label.BaseLabel):
838 """Label indicating whether device has fingerprint sensor."""
839
840 _NAME = 'fingerprint'
841
842 def exists(self, host):
843 return host.run('test -c /dev/cros_fp',
844 ignore_status=True).exit_status == 0
845
846
Garry Wang17a829e2019-03-20 12:03:18 -0700847class ReferenceDesignLabel(base_label.StringPrefixLabel):
848 """Determine the correct reference design label for the device. """
849
850 _NAME = 'reference_design'
851
852 def __init__(self):
853 self.response = None
854
855 def exists(self, host):
856 self.response = host.run('mosys platform family', ignore_status=True)
857 return self.response.exit_status == 0
858
859 def generate_labels(self, host):
860 if self.exists(host):
861 return [self.response.stdout.strip()]
862
863
Kevin Chenga2619dc2016-03-28 11:42:08 -0700864CROS_LABELS = [
865 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700866 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700867 AudioLoopbackDongleLabel(),
868 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000869 BoardLabel(),
C Shapiro05dd3222017-09-22 10:42:33 -0600870 ModelLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700871 ChameleonConnectionLabel(),
872 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800873 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700874 common_label.OSLabel(),
Mary Ruthven935ebad2018-06-13 16:13:20 -0700875 Cr50Label(),
Mary Ruthven6c462642019-09-17 19:13:36 -0700876 Cr50ROKeyidLabel(),
877 Cr50RWKeyidLabel(),
878 Cr50ROVersionLabel(),
879 Cr50RWVersionLabel(),
Ilja H. Friedel50290642017-12-01 19:39:53 -0800880 CtsArchLabel(),
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800881 DetachableBaseLabel(),
C Shapiro8315f5f2019-06-19 15:53:29 -0600882 DeviceSkuLabel(),
Ned Nguyene0a619d2019-07-01 15:50:23 -0600883 BrandCodeLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700884 ECLabel(),
Tom Hughese9552342018-12-18 14:29:25 -0800885 FingerprintLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700886 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700887 InternalDisplayLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700888 LucidSleepLabel(),
889 PowerSupplyLabel(),
Garry Wang17a829e2019-03-20 12:03:18 -0700890 ReferenceDesignLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700891 ServoLabel(),
892 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700893 VideoGlitchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700894]
Garry Wange4b6d6e2019-06-17 17:08:46 -0700895
896LABSTATION_LABELS = [
897 BoardLabel(),
898 ModelLabel(),
899 common_label.OSLabel(),
Garry Wang1d0598a2019-09-06 16:31:56 -0700900 PowerSupplyLabel(),
Garry Wange4b6d6e2019-06-17 17:08:46 -0700901]