blob: 0859de53d0663689a69528b1eb397c62ee0bb2a5 [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):
192 """Label indicating the cr50 version."""
193
194 _NAME = 'cr50'
195
196 def __init__(self):
197 self.ver = None
198
199
200 def exists(self, host):
201 # Make sure the gsctool version command runs ok
202 self.ver = host.run('gsctool -a -f', ignore_status=True)
203 return self.ver.exit_status == 0
204
205
206 def generate_labels(self, host):
207 # Check the major version to determine prePVT vs PVT
Mary Ruthven35207022019-04-15 11:57:29 -0700208 version_info = re.search('RW (\d+\.(\d+)\.\d+)$', self.ver.stdout)
209 full_version = version_info.group(1)
210 major_version = int(version_info.group(2))
Mary Ruthven935ebad2018-06-13 16:13:20 -0700211 # PVT images have a odd major version prePVT have even
Mary Ruthven35207022019-04-15 11:57:29 -0700212 return [full_version, 'pvt' if (major_version % 2) else 'prepvt']
Mary Ruthven935ebad2018-06-13 16:13:20 -0700213
214
Kevin Chenga2619dc2016-03-28 11:42:08 -0700215class AccelsLabel(base_label.BaseLabel):
216 """Determine the type of accelerometers on this host."""
217
218 _NAME = 'accel:cros-ec'
219
220 def exists(self, host):
221 # Check to make sure we have ectool
222 rv = host.run('which ectool', ignore_status=True)
223 if rv.exit_status:
224 logging.info("No ectool cmd found; assuming no EC accelerometers")
225 return False
226
227 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700228 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700229 if rv.exit_status:
230 logging.info("EC does not support motionsense command; "
231 "assuming no EC accelerometers")
232 return False
233
234 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700235 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700236 if active[0] == "0":
237 logging.info("Motion sense inactive; assuming no EC accelerometers")
238 return False
239
240 logging.info("EC accelerometers found")
241 return True
242
243
244class ChameleonLabel(base_label.BaseLabel):
245 """Determine if a Chameleon is connected to this host."""
246
247 _NAME = 'chameleon'
248
249 def exists(self, host):
howardchung83e55272019-08-08 14:08:05 +0800250 return len(host._chameleon_host_list) > 0
Kevin Chenga2619dc2016-03-28 11:42:08 -0700251
252
253class ChameleonConnectionLabel(base_label.StringPrefixLabel):
254 """Return the Chameleon connection label."""
255
256 _NAME = 'chameleon'
257
258 def exists(self, host):
howardchung83e55272019-08-08 14:08:05 +0800259 return len(host._chameleon_host_list) > 0
Kevin Chenga2619dc2016-03-28 11:42:08 -0700260
Joseph Hwangeac44312016-08-31 12:08:38 +0800261
Kevin Chenga2619dc2016-03-28 11:42:08 -0700262 def generate_labels(self, host):
howardchung83e55272019-08-08 14:08:05 +0800263 return [[chameleon.get_label()] for chameleon in host.chameleon_list]
Kevin Chenga2619dc2016-03-28 11:42:08 -0700264
265
Joseph Hwangeac44312016-08-31 12:08:38 +0800266class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
267 """Return the Chameleon peripherals labels.
268
269 The 'chameleon:bt_hid' label is applied if the bluetooth
270 classic hid device, i.e, RN-42 emulation kit, is detected.
271
272 Any peripherals plugged into the chameleon board would be
273 detected and applied proper labels in this class.
274 """
275
276 _NAME = 'chameleon'
277
278 def exists(self, host):
howardchung83e55272019-08-08 14:08:05 +0800279 return len(host._chameleon_host_list) > 0
Joseph Hwangeac44312016-08-31 12:08:38 +0800280
281
282 def generate_labels(self, host):
howardchung83e55272019-08-08 14:08:05 +0800283 labels_list = []
284
285 for chameleon, chameleon_host in \
286 zip(host.chameleon_list, host._chameleon_host_list):
287 labels = []
288 try:
289 bt_hid_device = chameleon.get_bluetooth_hid_mouse()
290 if bt_hid_device.CheckSerialConnection():
291 labels.append('bt_hid')
292 except:
293 logging.error('Error with initializing bt_hid_mouse on '
294 'chameleon %s', chameleon_host.hostname)
295
296 try:
297 ble_hid_device = chameleon.get_ble_mouse()
298 if ble_hid_device.CheckSerialConnection():
299 labels.append('bt_ble_hid')
300 except:
301 logging.error('Error with initializing ble_hid_mouse on '
302 'chameleon %s', chameleon_host.hostname)
303
304 try:
305 bt_a2dp_sink = chameleon.get_bluetooth_a2dp_sink()
306 if bt_a2dp_sink.CheckSerialConnection():
307 labels.append('bt_a2dp_sink')
308 except:
309 logging.error('Error with initializing bt_a2dp_sink on '
310 'chameleon %s', chameleon_host.hostname)
311
312 if labels != []:
313 labels.append('bt_peer')
314
315 if host.multi_chameleon:
316 labels_list.append(labels)
317 else:
318 labels_list.extend(labels)
319
320
321 logging.info('Bluetooth labels are %s', labels_list)
322 return labels_list
Shijin Abrahamff61ac32019-05-20 12:35:44 -0700323
324
Joseph Hwangeac44312016-08-31 12:08:38 +0800325
326
Kevin Chenga2619dc2016-03-28 11:42:08 -0700327class AudioLoopbackDongleLabel(base_label.BaseLabel):
328 """Return the label if an audio loopback dongle is plugged in."""
329
330 _NAME = 'audio_loopback_dongle'
331
332 def exists(self, host):
Gregory Nisbete280ea22019-08-16 17:50:03 -0700333 # Based on crbug.com/991285, AudioLoopbackDongle sometimes flips.
334 # Ensure that AudioLoopbackDongle.exists returns True
335 # forever, after it returns True *once*.
336 if self._cached_exists(host):
337 # If the current state is True, return it, don't run the command on
338 # the DUT and potentially flip the state.
339 return True
340 # If the current state is not True, run the command on
341 # the DUT. The new state will be set to whatever the command
342 # produces.
343 return self._host_run_exists(host)
344
345 def _cached_exists(self, host):
346 """Get the state of AudioLoopbackDongle in the data store"""
347 info = host.host_info_store.get()
348 for label in info.labels:
349 if label.startswith(self._NAME):
350 return True
351 return False
352
353 def _host_run_exists(self, host):
354 """Detect presence of audio_loopback_dongle by physically
355 running a command on the DUT."""
Kevin Chenga2619dc2016-03-28 11:42:08 -0700356 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
357 ignore_status=True).stdout
358 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
359 cras_utils.node_type_is_plugged('MIC', nodes_info)):
360 return True
361 return False
362
363
364class PowerSupplyLabel(base_label.StringPrefixLabel):
365 """
366 Return the label describing the power supply type.
367
368 Labels representing this host's power supply.
369 * `power:battery` when the device has a battery intended for
370 extended use
371 * `power:AC_primary` when the device has a battery not intended
372 for extended use (for moving the machine, etc)
373 * `power:AC_only` when the device has no battery at all.
374 """
375
376 _NAME = 'power'
377
378 def __init__(self):
379 self.psu_cmd_result = None
380
381
382 def exists(self, host):
383 self.psu_cmd_result = host.run(command='mosys psu type',
384 ignore_status=True)
385 return self.psu_cmd_result.stdout.strip() != 'unknown'
386
387
388 def generate_labels(self, host):
389 if self.psu_cmd_result.exit_status:
390 # The psu command for mosys is not included for all platforms. The
391 # assumption is that the device will have a battery if the command
392 # is not found.
393 return ['battery']
394 return [self.psu_cmd_result.stdout.strip()]
395
396
397class StorageLabel(base_label.StringPrefixLabel):
398 """
399 Return the label describing the storage type.
400
401 Determine if the internal device is SCSI or dw_mmc device.
402 Then check that it is SSD or HDD or eMMC or something else.
403
404 Labels representing this host's internal device type:
405 * `storage:ssd` when internal device is solid state drive
406 * `storage:hdd` when internal device is hard disk drive
407 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700408 * `storage:nvme` when internal device is NVMe drive
Alexis Savery570e7fb2018-06-26 10:48:15 -0700409 * `storage:ufs` when internal device is ufs drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700410 * None When internal device is something else or
411 when we are unable to determine the type
412 """
413
414 _NAME = 'storage'
415
416 def __init__(self):
417 self.type_str = ''
418
419
420 def exists(self, host):
421 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
422 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
423 '. /usr/share/misc/chromeos-common.sh;',
424 'load_base_vars;',
425 'get_fixed_dst_drive'])
426 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
427 if rootdev.exit_status:
428 logging.info("Fail to run %s", rootdev_cmd)
429 return False
430 rootdev_str = rootdev.stdout.strip()
431
432 if not rootdev_str:
433 return False
434
435 rootdev_base = os.path.basename(rootdev_str)
436
437 mmc_pattern = '/dev/mmcblk[0-9]'
438 if re.match(mmc_pattern, rootdev_str):
439 # Use type to determine if the internal device is eMMC or somthing
440 # else. We can assume that MMC is always an internal device.
441 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
442 type = host.run(command=type_cmd, ignore_status=True)
443 if type.exit_status:
444 logging.info("Fail to run %s", type_cmd)
445 return False
446 type_str = type.stdout.strip()
447
448 if type_str == 'MMC':
449 self.type_str = 'mmc'
450 return True
451
452 scsi_pattern = '/dev/sd[a-z]+'
453 if re.match(scsi_pattern, rootdev.stdout):
454 # Read symlink for /sys/block/sd* to determine if the internal
455 # device is connected via ata or usb.
456 link_cmd = 'readlink /sys/block/%s' % rootdev_base
457 link = host.run(command=link_cmd, ignore_status=True)
458 if link.exit_status:
459 logging.info("Fail to run %s", link_cmd)
460 return False
461 link_str = link.stdout.strip()
462 if 'usb' in link_str:
463 return False
Alexis Savery570e7fb2018-06-26 10:48:15 -0700464 elif 'ufs' in link_str:
465 self.type_str = 'ufs'
466 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700467
468 # Read rotation to determine if the internal device is ssd or hdd.
469 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
470 % rootdev_base)
471 rotate = host.run(command=rotate_cmd, ignore_status=True)
472 if rotate.exit_status:
473 logging.info("Fail to run %s", rotate_cmd)
474 return False
475 rotate_str = rotate.stdout.strip()
476
477 rotate_dict = {'0':'ssd', '1':'hdd'}
478 self.type_str = rotate_dict.get(rotate_str)
479 return True
480
Gwendal Grignou327fec62017-07-26 15:25:43 -0700481 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
482 if re.match(nvme_pattern, rootdev_str):
Gwendal Grignou3660c192017-12-06 10:11:23 -0800483 self.type_str = 'nvme'
Gwendal Grignou327fec62017-07-26 15:25:43 -0700484 return True
485
Kevin Chenga2619dc2016-03-28 11:42:08 -0700486 # All other internal device / error case will always fall here
487 return False
488
489
490 def generate_labels(self, host):
491 return [self.type_str]
492
493
494class ServoLabel(base_label.BaseLabel):
495 """Label to apply if a servo is present."""
496
497 _NAME = 'servo'
498
499 def exists(self, host):
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700500 # Based on crbug.com/995900, Servo sometimes flips.
501 # Ensure that ServoLabel.exists returns True
502 # forever, after it returns True *once*.
503 if self._cached_exists(host):
504 # If the current state is True, return it, don't run the command on
505 # the DUT and potentially flip the state.
506 return True
507 # If the current state is not True, run the command on
508 # the DUT. The new state will be set to whatever the command
509 # produces.
510 return self._host_run_exists(host)
511
512 def _cached_exists(self, host):
513 """Get the state of Servo in the data store"""
514 info = host.host_info_store.get()
515 for label in info.labels:
516 if label.startswith(self._NAME):
517 return True
518 return False
519
520 def _host_run_exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700521 """
522 Check if the servo label should apply to the host or not.
523
524 @returns True if a servo host is detected, False otherwise.
525 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700526 servo_host_hostname = None
Prathmesh Prabhu37ae79b2018-09-12 10:37:44 -0700527 servo_args = servo_host.get_servo_args_for_host(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700528 if servo_args:
529 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700530 return (servo_host_hostname is not None
531 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700532
533
Ilja H. Friedel50290642017-12-01 19:39:53 -0800534class ArcLabel(base_label.BaseLabel):
535 """Label indicates if host has ARC support."""
536
537 _NAME = 'arc'
538
539 @base_label.forever_exists_decorate
540 def exists(self, host):
541 return 0 == host.run(
542 'grep CHROMEOS_ARC_VERSION /etc/lsb-release',
543 ignore_status=True).exit_status
544
545
546class CtsArchLabel(base_label.StringLabel):
547 """Labels to determine the abi of the CTS bundle (arm or x86 only)."""
Rohit Makasana5a153502016-06-13 15:50:09 -0700548
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700549 _NAME = ['cts_abi_arm', 'cts_abi_x86', 'cts_cpu_arm', 'cts_cpu_x86']
Rohit Makasana5a153502016-06-13 15:50:09 -0700550
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700551 def _get_cts_abis(self, arch):
Rohit Makasana5a153502016-06-13 15:50:09 -0700552 """Return supported CTS ABIs.
553
554 @return List of supported CTS bundle ABIs.
555 """
556 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700557 return cts_abis.get(arch, [])
558
559 def _get_cts_cpus(self, arch):
560 """Return supported CTS native CPUs.
561
562 This is needed for CTS_Instant scheduling.
563 @return List of supported CTS native CPUs.
564 """
565 cts_cpus = {'x86_64': ['x86'], 'arm': ['arm']}
566 return cts_cpus.get(arch, [])
Rohit Makasana5a153502016-06-13 15:50:09 -0700567
Rohit Makasana5a153502016-06-13 15:50:09 -0700568 def generate_labels(self, host):
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700569 cpu_arch = host.get_cpu_arch()
570 abi_labels = ['cts_abi_' + abi for abi in self._get_cts_abis(cpu_arch)]
571 cpu_labels = ['cts_cpu_' + cpu for cpu in self._get_cts_cpus(cpu_arch)]
572 return abi_labels + cpu_labels
Rohit Makasana5a153502016-06-13 15:50:09 -0700573
574
Kevin Chenga2619dc2016-03-28 11:42:08 -0700575class VideoGlitchLabel(base_label.BaseLabel):
576 """Label indicates if host supports video glitch detection tests."""
577
578 _NAME = 'video_glitch_detection'
579
580 def exists(self, host):
581 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
582
583 return board in video_test_constants.SUPPORTED_BOARDS
584
585
Kevin Chenga2619dc2016-03-28 11:42:08 -0700586class InternalDisplayLabel(base_label.StringLabel):
587 """Label that determines if the device has an internal display."""
588
589 _NAME = 'internal_display'
590
591 def generate_labels(self, host):
592 from autotest_lib.client.cros.graphics import graphics_utils
593 from autotest_lib.client.common_lib import utils as common_utils
594
595 def __system_output(cmd):
596 return host.run(cmd).stdout
597
598 def __read_file(remote_path):
599 return host.run('cat %s' % remote_path).stdout
600
601 # Hijack the necessary client functions so that we can take advantage
602 # of the client lib here.
603 # FIXME: find a less hacky way than this
604 original_system_output = utils.system_output
605 original_read_file = common_utils.read_file
606 utils.system_output = __system_output
607 common_utils.read_file = __read_file
608 try:
609 return ([self._NAME]
610 if graphics_utils.has_internal_display()
611 else [])
612 finally:
613 utils.system_output = original_system_output
614 common_utils.read_file = original_read_file
615
616
617class LucidSleepLabel(base_label.BaseLabel):
618 """Label that determines if device has support for lucid sleep."""
619
620 # TODO(kevcheng): See if we can determine if this label is applicable a
621 # better way (crbug.com/592146).
622 _NAME = 'lucidsleep'
RaviChandra Sadinenic06b00e2018-11-03 09:56:11 -0700623 LUCID_SLEEP_BOARDS = ['nocturne', 'poppy']
Kevin Chenga2619dc2016-03-28 11:42:08 -0700624
625 def exists(self, host):
626 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
627 return board in self.LUCID_SLEEP_BOARDS
628
629
Kevin Cheng80ad5732016-03-31 16:01:56 -0700630class HWIDLabel(base_label.StringLabel):
631 """Return all the labels generated from the hwid."""
632
633 # We leave out _NAME because hwid_lib will generate everything for us.
634
635 def __init__(self):
636 # Grab the key file needed to access the hwid service.
637 self.key_file = global_config.global_config.get_config_value(
638 'CROS', 'HWID_KEY', type=str)
639
640
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700641 @staticmethod
642 def _merge_hwid_label_lists(new, old):
643 """merge a list of old and new values for hwid_labels.
644 preferring new values if available
645
646 @returns: list of labels"""
647 # TODO(gregorynisbet): what is the appropriate way to merge
648 # old and new information?
649 retained = set(x for x in old)
650 for label in new:
651 key, sep, value = label.partition(':')
652 # If we have a key-value key such as variant:aaa,
653 # then we remove all the old labels with the same key.
654 if sep:
655 retained = set(x for x in retained if (not x.startswith(key + ':')))
656 return list(sorted(retained.union(new)))
657
658
659 def _hwid_label_names(self):
660 """get the labels that hwid_lib controls.
661
662 @returns: hwid_labels
663 """
664 all_hwid_labels, _ = self.get_all_labels()
665 # If and only if get_all_labels was unsuccessful,
666 # it will return a falsey value.
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700667 out = all_hwid_labels or HWID_LABELS_FALLBACK
668
669 # TODO(gregorynisbet): remove this
670 # TODO(crbug.com/999785)
671 if "sku" not in out:
672 logging.info("sku-less label names %s", out)
673
674 return out
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700675
676
677 def _old_label_values(self, host):
678 """get the hwid_lib labels on previous run
679
680 @returns: hwid_labels"""
681 out = []
682 info = host.host_info_store.get()
683 for hwid_label in self._hwid_label_names():
684 for label in info.labels:
685 # NOTE: we want *all* the labels starting
686 # with this prefix.
687 if label.startswith(hwid_label):
688 out.append(label)
689 return out
690
691
Kevin Cheng80ad5732016-03-31 16:01:56 -0700692 def generate_labels(self, host):
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700693 # use previous values as default
694 old_hwid_labels = self._old_label_values(host)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700695 hwid = host.run_output('crossystem hwid').strip()
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700696 hwid_info_list = []
697 try:
698 hwid_info_response = hwid_lib.get_hwid_info(
699 hwid=hwid,
700 info_type=hwid_lib.HWID_INFO_LABEL,
701 key_file=self.key_file,
702 )
703 hwid_info_list = hwid_info_response.get('labels', [])
704 except hwid_lib.HwIdException as e:
705 logging.info("HwIdException: %s", e)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700706
707 for hwid_info in hwid_info_list:
Gregory Nisbet4d71e1e2019-08-30 16:02:03 -0700708 # TODO(gregorynisbet): if hwid_info is not a list, something has
709 # gone horribly wrong. Here we log the offending hwid_info object
710 # and just continue with the old values.
711 if not isinstance(hwid_info, dict):
712 logging.info("hwid_info: %s", hwid_info)
713 return old_hwid_labels
Kevin Cheng80ad5732016-03-31 16:01:56 -0700714 # If it's a prefix, we'll have:
715 # {'name': prefix_label, 'value': postfix_label} and create
716 # 'prefix_label:postfix_label'; otherwise it'll just be
717 # {'name': label} which should just be 'label'.
718 value = hwid_info.get('value', '')
719 name = hwid_info.get('name', '')
720 # There should always be a name but just in case there is not.
721 if name:
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700722 new_label = name if not value else '%s:%s' % (name, value)
723 hwid_info_list.append(new_label)
724
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700725 # TODO(gregorynisbet): remove this
726 # TODO(crbug.com/999785)
727 logging.info("old_hwid_labels %s", old_hwid_labels)
728 logging.info("hwid_info_list %s", hwid_info_list)
729
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700730 return HWIDLabel._merge_hwid_label_lists(
731 old=old_hwid_labels,
732 new=hwid_info_list,
733 )
Kevin Cheng80ad5732016-03-31 16:01:56 -0700734
735
736 def get_all_labels(self):
737 """We need to try all labels as a prefix and as standalone.
738
739 We don't know for sure which labels are prefix labels and which are
740 standalone so we try all of them as both.
741 """
742 all_hwid_labels = []
743 try:
744 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
745 self.key_file)
746 except IOError:
747 logging.error('Can not open key file: %s', self.key_file)
748 except hwid_lib.HwIdException as e:
749 logging.error('hwid service: %s', e)
750 return all_hwid_labels, all_hwid_labels
751
752
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800753class DetachableBaseLabel(base_label.BaseLabel):
754 """Label indicating if device has detachable keyboard."""
755
756 _NAME = 'detachablebase'
757
758 def exists(self, host):
759 return host.run('which hammerd', ignore_status=True).exit_status == 0
760
761
Tom Hughese9552342018-12-18 14:29:25 -0800762class FingerprintLabel(base_label.BaseLabel):
763 """Label indicating whether device has fingerprint sensor."""
764
765 _NAME = 'fingerprint'
766
767 def exists(self, host):
768 return host.run('test -c /dev/cros_fp',
769 ignore_status=True).exit_status == 0
770
771
Garry Wang17a829e2019-03-20 12:03:18 -0700772class ReferenceDesignLabel(base_label.StringPrefixLabel):
773 """Determine the correct reference design label for the device. """
774
775 _NAME = 'reference_design'
776
777 def __init__(self):
778 self.response = None
779
780 def exists(self, host):
781 self.response = host.run('mosys platform family', ignore_status=True)
782 return self.response.exit_status == 0
783
784 def generate_labels(self, host):
785 if self.exists(host):
786 return [self.response.stdout.strip()]
787
788
Kevin Chenga2619dc2016-03-28 11:42:08 -0700789CROS_LABELS = [
790 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700791 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700792 AudioLoopbackDongleLabel(),
793 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000794 BoardLabel(),
C Shapiro05dd3222017-09-22 10:42:33 -0600795 ModelLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700796 ChameleonConnectionLabel(),
797 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800798 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700799 common_label.OSLabel(),
Mary Ruthven935ebad2018-06-13 16:13:20 -0700800 Cr50Label(),
Ilja H. Friedel50290642017-12-01 19:39:53 -0800801 CtsArchLabel(),
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800802 DetachableBaseLabel(),
C Shapiro8315f5f2019-06-19 15:53:29 -0600803 DeviceSkuLabel(),
Ned Nguyene0a619d2019-07-01 15:50:23 -0600804 BrandCodeLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700805 ECLabel(),
Tom Hughese9552342018-12-18 14:29:25 -0800806 FingerprintLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700807 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700808 InternalDisplayLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700809 LucidSleepLabel(),
810 PowerSupplyLabel(),
Garry Wang17a829e2019-03-20 12:03:18 -0700811 ReferenceDesignLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700812 ServoLabel(),
813 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700814 VideoGlitchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700815]
Garry Wange4b6d6e2019-06-17 17:08:46 -0700816
817LABSTATION_LABELS = [
818 BoardLabel(),
819 ModelLabel(),
820 common_label.OSLabel(),
821]