blob: ebb8b3354fd3fe29343d24900bb490d1a96e59f1 [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
130 mosys_cmd = 'mosys platform brand'
131 result = host.run(command=mosys_cmd, ignore_status=True)
132 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):
howardchung83e55272019-08-08 14:08:05 +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
369 if labels != []:
370 labels.append('bt_peer')
371
372 if host.multi_chameleon:
373 labels_list.append(labels)
374 else:
375 labels_list.extend(labels)
376
377
378 logging.info('Bluetooth labels are %s', labels_list)
379 return labels_list
Shijin Abrahamff61ac32019-05-20 12:35:44 -0700380
381
Joseph Hwangeac44312016-08-31 12:08:38 +0800382
383
Kevin Chenga2619dc2016-03-28 11:42:08 -0700384class AudioLoopbackDongleLabel(base_label.BaseLabel):
385 """Return the label if an audio loopback dongle is plugged in."""
386
387 _NAME = 'audio_loopback_dongle'
388
389 def exists(self, host):
Gregory Nisbete280ea22019-08-16 17:50:03 -0700390 # Based on crbug.com/991285, AudioLoopbackDongle sometimes flips.
391 # Ensure that AudioLoopbackDongle.exists returns True
392 # forever, after it returns True *once*.
393 if self._cached_exists(host):
394 # If the current state is True, return it, don't run the command on
395 # the DUT and potentially flip the state.
396 return True
397 # If the current state is not True, run the command on
398 # the DUT. The new state will be set to whatever the command
399 # produces.
400 return self._host_run_exists(host)
401
402 def _cached_exists(self, host):
403 """Get the state of AudioLoopbackDongle in the data store"""
404 info = host.host_info_store.get()
405 for label in info.labels:
406 if label.startswith(self._NAME):
407 return True
408 return False
409
410 def _host_run_exists(self, host):
411 """Detect presence of audio_loopback_dongle by physically
412 running a command on the DUT."""
Kevin Chenga2619dc2016-03-28 11:42:08 -0700413 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
414 ignore_status=True).stdout
415 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
416 cras_utils.node_type_is_plugged('MIC', nodes_info)):
417 return True
418 return False
419
420
421class PowerSupplyLabel(base_label.StringPrefixLabel):
422 """
423 Return the label describing the power supply type.
424
425 Labels representing this host's power supply.
426 * `power:battery` when the device has a battery intended for
427 extended use
428 * `power:AC_primary` when the device has a battery not intended
429 for extended use (for moving the machine, etc)
430 * `power:AC_only` when the device has no battery at all.
431 """
432
433 _NAME = 'power'
434
435 def __init__(self):
436 self.psu_cmd_result = None
437
438
439 def exists(self, host):
440 self.psu_cmd_result = host.run(command='mosys psu type',
441 ignore_status=True)
442 return self.psu_cmd_result.stdout.strip() != 'unknown'
443
444
445 def generate_labels(self, host):
446 if self.psu_cmd_result.exit_status:
447 # The psu command for mosys is not included for all platforms. The
448 # assumption is that the device will have a battery if the command
449 # is not found.
450 return ['battery']
451 return [self.psu_cmd_result.stdout.strip()]
452
453
454class StorageLabel(base_label.StringPrefixLabel):
455 """
456 Return the label describing the storage type.
457
458 Determine if the internal device is SCSI or dw_mmc device.
459 Then check that it is SSD or HDD or eMMC or something else.
460
461 Labels representing this host's internal device type:
462 * `storage:ssd` when internal device is solid state drive
463 * `storage:hdd` when internal device is hard disk drive
464 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700465 * `storage:nvme` when internal device is NVMe drive
Alexis Savery570e7fb2018-06-26 10:48:15 -0700466 * `storage:ufs` when internal device is ufs drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700467 * None When internal device is something else or
468 when we are unable to determine the type
469 """
470
471 _NAME = 'storage'
472
473 def __init__(self):
474 self.type_str = ''
475
476
477 def exists(self, host):
478 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
479 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
480 '. /usr/share/misc/chromeos-common.sh;',
481 'load_base_vars;',
482 'get_fixed_dst_drive'])
483 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
484 if rootdev.exit_status:
485 logging.info("Fail to run %s", rootdev_cmd)
486 return False
487 rootdev_str = rootdev.stdout.strip()
488
489 if not rootdev_str:
490 return False
491
492 rootdev_base = os.path.basename(rootdev_str)
493
494 mmc_pattern = '/dev/mmcblk[0-9]'
495 if re.match(mmc_pattern, rootdev_str):
496 # Use type to determine if the internal device is eMMC or somthing
497 # else. We can assume that MMC is always an internal device.
498 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
499 type = host.run(command=type_cmd, ignore_status=True)
500 if type.exit_status:
501 logging.info("Fail to run %s", type_cmd)
502 return False
503 type_str = type.stdout.strip()
504
505 if type_str == 'MMC':
506 self.type_str = 'mmc'
507 return True
508
509 scsi_pattern = '/dev/sd[a-z]+'
510 if re.match(scsi_pattern, rootdev.stdout):
511 # Read symlink for /sys/block/sd* to determine if the internal
512 # device is connected via ata or usb.
513 link_cmd = 'readlink /sys/block/%s' % rootdev_base
514 link = host.run(command=link_cmd, ignore_status=True)
515 if link.exit_status:
516 logging.info("Fail to run %s", link_cmd)
517 return False
518 link_str = link.stdout.strip()
519 if 'usb' in link_str:
520 return False
Alexis Savery570e7fb2018-06-26 10:48:15 -0700521 elif 'ufs' in link_str:
522 self.type_str = 'ufs'
523 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700524
525 # Read rotation to determine if the internal device is ssd or hdd.
526 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
527 % rootdev_base)
528 rotate = host.run(command=rotate_cmd, ignore_status=True)
529 if rotate.exit_status:
530 logging.info("Fail to run %s", rotate_cmd)
531 return False
532 rotate_str = rotate.stdout.strip()
533
534 rotate_dict = {'0':'ssd', '1':'hdd'}
535 self.type_str = rotate_dict.get(rotate_str)
536 return True
537
Gwendal Grignou327fec62017-07-26 15:25:43 -0700538 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
539 if re.match(nvme_pattern, rootdev_str):
Gwendal Grignou3660c192017-12-06 10:11:23 -0800540 self.type_str = 'nvme'
Gwendal Grignou327fec62017-07-26 15:25:43 -0700541 return True
542
Kevin Chenga2619dc2016-03-28 11:42:08 -0700543 # All other internal device / error case will always fall here
544 return False
545
546
547 def generate_labels(self, host):
548 return [self.type_str]
549
550
551class ServoLabel(base_label.BaseLabel):
552 """Label to apply if a servo is present."""
553
554 _NAME = 'servo'
555
556 def exists(self, host):
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700557 # Based on crbug.com/995900, Servo sometimes flips.
558 # Ensure that ServoLabel.exists returns True
559 # forever, after it returns True *once*.
560 if self._cached_exists(host):
561 # If the current state is True, return it, don't run the command on
562 # the DUT and potentially flip the state.
563 return True
564 # If the current state is not True, run the command on
565 # the DUT. The new state will be set to whatever the command
566 # produces.
567 return self._host_run_exists(host)
568
569 def _cached_exists(self, host):
570 """Get the state of Servo in the data store"""
571 info = host.host_info_store.get()
572 for label in info.labels:
573 if label.startswith(self._NAME):
574 return True
575 return False
576
577 def _host_run_exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700578 """
579 Check if the servo label should apply to the host or not.
580
581 @returns True if a servo host is detected, False otherwise.
582 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700583 servo_host_hostname = None
Prathmesh Prabhu37ae79b2018-09-12 10:37:44 -0700584 servo_args = servo_host.get_servo_args_for_host(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700585 if servo_args:
586 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700587 return (servo_host_hostname is not None
588 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700589
590
Ilja H. Friedel50290642017-12-01 19:39:53 -0800591class ArcLabel(base_label.BaseLabel):
592 """Label indicates if host has ARC support."""
593
594 _NAME = 'arc'
595
596 @base_label.forever_exists_decorate
597 def exists(self, host):
598 return 0 == host.run(
599 'grep CHROMEOS_ARC_VERSION /etc/lsb-release',
600 ignore_status=True).exit_status
601
602
603class CtsArchLabel(base_label.StringLabel):
604 """Labels to determine the abi of the CTS bundle (arm or x86 only)."""
Rohit Makasana5a153502016-06-13 15:50:09 -0700605
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700606 _NAME = ['cts_abi_arm', 'cts_abi_x86', 'cts_cpu_arm', 'cts_cpu_x86']
Rohit Makasana5a153502016-06-13 15:50:09 -0700607
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700608 def _get_cts_abis(self, arch):
Rohit Makasana5a153502016-06-13 15:50:09 -0700609 """Return supported CTS ABIs.
610
611 @return List of supported CTS bundle ABIs.
612 """
613 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700614 return cts_abis.get(arch, [])
615
616 def _get_cts_cpus(self, arch):
617 """Return supported CTS native CPUs.
618
619 This is needed for CTS_Instant scheduling.
620 @return List of supported CTS native CPUs.
621 """
622 cts_cpus = {'x86_64': ['x86'], 'arm': ['arm']}
623 return cts_cpus.get(arch, [])
Rohit Makasana5a153502016-06-13 15:50:09 -0700624
Rohit Makasana5a153502016-06-13 15:50:09 -0700625 def generate_labels(self, host):
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700626 cpu_arch = host.get_cpu_arch()
627 abi_labels = ['cts_abi_' + abi for abi in self._get_cts_abis(cpu_arch)]
628 cpu_labels = ['cts_cpu_' + cpu for cpu in self._get_cts_cpus(cpu_arch)]
629 return abi_labels + cpu_labels
Rohit Makasana5a153502016-06-13 15:50:09 -0700630
631
Kevin Chenga2619dc2016-03-28 11:42:08 -0700632class VideoGlitchLabel(base_label.BaseLabel):
633 """Label indicates if host supports video glitch detection tests."""
634
635 _NAME = 'video_glitch_detection'
636
637 def exists(self, host):
638 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
639
640 return board in video_test_constants.SUPPORTED_BOARDS
641
642
Kevin Chenga2619dc2016-03-28 11:42:08 -0700643class InternalDisplayLabel(base_label.StringLabel):
644 """Label that determines if the device has an internal display."""
645
646 _NAME = 'internal_display'
647
648 def generate_labels(self, host):
649 from autotest_lib.client.cros.graphics import graphics_utils
650 from autotest_lib.client.common_lib import utils as common_utils
651
652 def __system_output(cmd):
653 return host.run(cmd).stdout
654
655 def __read_file(remote_path):
656 return host.run('cat %s' % remote_path).stdout
657
658 # Hijack the necessary client functions so that we can take advantage
659 # of the client lib here.
660 # FIXME: find a less hacky way than this
661 original_system_output = utils.system_output
662 original_read_file = common_utils.read_file
663 utils.system_output = __system_output
664 common_utils.read_file = __read_file
665 try:
666 return ([self._NAME]
667 if graphics_utils.has_internal_display()
668 else [])
669 finally:
670 utils.system_output = original_system_output
671 common_utils.read_file = original_read_file
672
673
674class LucidSleepLabel(base_label.BaseLabel):
675 """Label that determines if device has support for lucid sleep."""
676
677 # TODO(kevcheng): See if we can determine if this label is applicable a
678 # better way (crbug.com/592146).
679 _NAME = 'lucidsleep'
RaviChandra Sadinenic06b00e2018-11-03 09:56:11 -0700680 LUCID_SLEEP_BOARDS = ['nocturne', 'poppy']
Kevin Chenga2619dc2016-03-28 11:42:08 -0700681
682 def exists(self, host):
683 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
684 return board in self.LUCID_SLEEP_BOARDS
685
686
Xixuan Wu78569d02019-09-15 16:08:25 -0700687def _parse_hwid_labels(hwid_info_list):
688 if len(hwid_info_list) == 0:
689 return hwid_info_list
690
691 res = []
692 # See crbug.com/997816#c7 for details of two potential formats of returns
693 # from HWID server.
694 if isinstance(hwid_info_list[0], dict):
695 # Format of hwid_info:
696 # [{u'name': u'sku', u'value': u'xxx'}, ..., ]
697 for hwid_info in hwid_info_list:
698 value = hwid_info.get('value', '')
699 name = hwid_info.get('name', '')
700 # There should always be a name but just in case there is not.
701 if name:
702 new_label = name if not value else '%s:%s' % (name, value)
703 res.append(new_label)
704 else:
705 # Format of hwid_info:
706 # [<DUTLabel name: 'sku' value: u'xxx'>, ..., ]
707 for hwid_info in hwid_info_list:
708 new_label = str(hwid_info)
709 logging.info('processing hwid label: %s', new_label)
710 res.append(new_label)
711
712 return res
713
714
Kevin Cheng80ad5732016-03-31 16:01:56 -0700715class HWIDLabel(base_label.StringLabel):
716 """Return all the labels generated from the hwid."""
717
718 # We leave out _NAME because hwid_lib will generate everything for us.
719
720 def __init__(self):
721 # Grab the key file needed to access the hwid service.
722 self.key_file = global_config.global_config.get_config_value(
723 'CROS', 'HWID_KEY', type=str)
724
725
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700726 @staticmethod
727 def _merge_hwid_label_lists(new, old):
728 """merge a list of old and new values for hwid_labels.
729 preferring new values if available
730
731 @returns: list of labels"""
732 # TODO(gregorynisbet): what is the appropriate way to merge
733 # old and new information?
734 retained = set(x for x in old)
735 for label in new:
736 key, sep, value = label.partition(':')
737 # If we have a key-value key such as variant:aaa,
738 # then we remove all the old labels with the same key.
739 if sep:
740 retained = set(x for x in retained if (not x.startswith(key + ':')))
741 return list(sorted(retained.union(new)))
742
743
744 def _hwid_label_names(self):
745 """get the labels that hwid_lib controls.
746
747 @returns: hwid_labels
748 """
749 all_hwid_labels, _ = self.get_all_labels()
750 # If and only if get_all_labels was unsuccessful,
751 # it will return a falsey value.
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700752 out = all_hwid_labels or HWID_LABELS_FALLBACK
753
754 # TODO(gregorynisbet): remove this
755 # TODO(crbug.com/999785)
756 if "sku" not in out:
757 logging.info("sku-less label names %s", out)
758
759 return out
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700760
761
762 def _old_label_values(self, host):
763 """get the hwid_lib labels on previous run
764
765 @returns: hwid_labels"""
766 out = []
767 info = host.host_info_store.get()
768 for hwid_label in self._hwid_label_names():
769 for label in info.labels:
770 # NOTE: we want *all* the labels starting
771 # with this prefix.
772 if label.startswith(hwid_label):
773 out.append(label)
774 return out
775
776
Kevin Cheng80ad5732016-03-31 16:01:56 -0700777 def generate_labels(self, host):
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700778 # use previous values as default
779 old_hwid_labels = self._old_label_values(host)
Xixuan Wue63f8352019-09-13 15:18:03 -0700780 logging.info("old_hwid_labels: %r", old_hwid_labels)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700781 hwid = host.run_output('crossystem hwid').strip()
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700782 hwid_info_list = []
783 try:
784 hwid_info_response = hwid_lib.get_hwid_info(
785 hwid=hwid,
786 info_type=hwid_lib.HWID_INFO_LABEL,
787 key_file=self.key_file,
788 )
Xixuan Wue63f8352019-09-13 15:18:03 -0700789 logging.info("hwid_info_response: %r", hwid_info_response)
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700790 hwid_info_list = hwid_info_response.get('labels', [])
791 except hwid_lib.HwIdException as e:
792 logging.info("HwIdException: %s", e)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700793
Xixuan Wu78569d02019-09-15 16:08:25 -0700794 new_hwid_labels = _parse_hwid_labels(hwid_info_list)
795 logging.info("new HWID labels: %r", new_hwid_labels)
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700796
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700797 return HWIDLabel._merge_hwid_label_lists(
798 old=old_hwid_labels,
Xixuan Wu78569d02019-09-15 16:08:25 -0700799 new=new_hwid_labels,
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700800 )
Kevin Cheng80ad5732016-03-31 16:01:56 -0700801
802
803 def get_all_labels(self):
804 """We need to try all labels as a prefix and as standalone.
805
806 We don't know for sure which labels are prefix labels and which are
807 standalone so we try all of them as both.
808 """
809 all_hwid_labels = []
810 try:
811 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
812 self.key_file)
813 except IOError:
814 logging.error('Can not open key file: %s', self.key_file)
815 except hwid_lib.HwIdException as e:
816 logging.error('hwid service: %s', e)
817 return all_hwid_labels, all_hwid_labels
818
819
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800820class DetachableBaseLabel(base_label.BaseLabel):
821 """Label indicating if device has detachable keyboard."""
822
823 _NAME = 'detachablebase'
824
825 def exists(self, host):
826 return host.run('which hammerd', ignore_status=True).exit_status == 0
827
828
Tom Hughese9552342018-12-18 14:29:25 -0800829class FingerprintLabel(base_label.BaseLabel):
830 """Label indicating whether device has fingerprint sensor."""
831
832 _NAME = 'fingerprint'
833
834 def exists(self, host):
835 return host.run('test -c /dev/cros_fp',
836 ignore_status=True).exit_status == 0
837
838
Garry Wang17a829e2019-03-20 12:03:18 -0700839class ReferenceDesignLabel(base_label.StringPrefixLabel):
840 """Determine the correct reference design label for the device. """
841
842 _NAME = 'reference_design'
843
844 def __init__(self):
845 self.response = None
846
847 def exists(self, host):
848 self.response = host.run('mosys platform family', ignore_status=True)
849 return self.response.exit_status == 0
850
851 def generate_labels(self, host):
852 if self.exists(host):
853 return [self.response.stdout.strip()]
854
855
Kevin Chenga2619dc2016-03-28 11:42:08 -0700856CROS_LABELS = [
857 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700858 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700859 AudioLoopbackDongleLabel(),
860 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000861 BoardLabel(),
C Shapiro05dd3222017-09-22 10:42:33 -0600862 ModelLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700863 ChameleonConnectionLabel(),
864 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800865 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700866 common_label.OSLabel(),
Mary Ruthven935ebad2018-06-13 16:13:20 -0700867 Cr50Label(),
Mary Ruthven6c462642019-09-17 19:13:36 -0700868 Cr50ROKeyidLabel(),
869 Cr50RWKeyidLabel(),
870 Cr50ROVersionLabel(),
871 Cr50RWVersionLabel(),
Ilja H. Friedel50290642017-12-01 19:39:53 -0800872 CtsArchLabel(),
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800873 DetachableBaseLabel(),
C Shapiro8315f5f2019-06-19 15:53:29 -0600874 DeviceSkuLabel(),
Ned Nguyene0a619d2019-07-01 15:50:23 -0600875 BrandCodeLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700876 ECLabel(),
Tom Hughese9552342018-12-18 14:29:25 -0800877 FingerprintLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700878 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700879 InternalDisplayLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700880 LucidSleepLabel(),
881 PowerSupplyLabel(),
Garry Wang17a829e2019-03-20 12:03:18 -0700882 ReferenceDesignLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700883 ServoLabel(),
884 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700885 VideoGlitchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700886]
Garry Wange4b6d6e2019-06-17 17:08:46 -0700887
888LABSTATION_LABELS = [
889 BoardLabel(),
890 ModelLabel(),
891 common_label.OSLabel(),
Garry Wang1d0598a2019-09-06 16:31:56 -0700892 PowerSupplyLabel(),
Garry Wange4b6d6e2019-06-17 17:08:46 -0700893]