blob: 54f9f2070b3e730b74fd02396414d33626d89a4e [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
Eshwar Narayan871a2c02020-02-06 11:15:24 -080030# Repair and Deploy taskName
31REPAIR_TASK_NAME = 'repair'
32DEPLOY_TASK_NAME = 'deploy'
33
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -070034
C Shapiro05dd3222017-09-22 10:42:33 -060035def _parse_lsb_output(host):
Allen Lia0c7afc2019-02-26 15:50:06 -080036 """Parses the LSB output and returns key data points for labeling.
C Shapiro05dd3222017-09-22 10:42:33 -060037
Allen Lia0c7afc2019-02-26 15:50:06 -080038 @param host: Host that the command will be executed against
39 @returns: LsbOutput with the result of parsing the /etc/lsb-release output
40 """
41 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
42 run_method=host.run)
C Shapiro05dd3222017-09-22 10:42:33 -060043
Allen Lia0c7afc2019-02-26 15:50:06 -080044 unibuild = release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1'
45 return LsbOutput(unibuild, release_info['CHROMEOS_RELEASE_BOARD'])
C Shapiro05dd3222017-09-22 10:42:33 -060046
Kevin Chenga2619dc2016-03-28 11:42:08 -070047
Kevin Chenga8455302016-08-31 20:54:41 +000048class BoardLabel(base_label.StringPrefixLabel):
49 """Determine the correct board label for the device."""
50
51 _NAME = ds_constants.BOARD_PREFIX.rstrip(':')
52
53 def generate_labels(self, host):
54 # We only want to apply the board labels once, which is when they get
55 # added to the AFE. That way we don't have to worry about the board
56 # label switching on us if the wrong builds get put on the devices.
57 # crbug.com/624207 records one event of the board label switching
58 # unexpectedly on us.
Allen Lia0c7afc2019-02-26 15:50:06 -080059 board = host.host_info_store.get().board
60 if board:
61 return [board]
Kevin Chenga8455302016-08-31 20:54:41 +000062 for label in host._afe_host.labels:
63 if label.startswith(self._NAME + ':'):
64 return [label.split(':')[-1]]
65
C Shapiro10970222017-10-24 08:55:55 -060066 return [_parse_lsb_output(host).board]
Kevin Chenga8455302016-08-31 20:54:41 +000067
68
C Shapirob05c00b2017-07-18 15:06:49 -060069class ModelLabel(base_label.StringPrefixLabel):
70 """Determine the correct model label for the device."""
71
72 _NAME = ds_constants.MODEL_LABEL
73
74 def generate_labels(self, host):
C Shapirod7ba4a72018-01-16 17:04:35 -070075 # Based on the issue explained in BoardLabel, return the existing
76 # label if it has already been set once.
Allen Lia0c7afc2019-02-26 15:50:06 -080077 model = host.host_info_store.get().model
78 if model:
79 return [model]
C Shapirod7ba4a72018-01-16 17:04:35 -070080 for label in host._afe_host.labels:
81 if label.startswith(self._NAME + ':'):
82 return [label.split(':')[-1]]
C Shapirob05c00b2017-07-18 15:06:49 -060083
C Shapiro32700032017-11-03 12:46:55 -060084 lsb_output = _parse_lsb_output(host)
85 model = None
86
87 if lsb_output.unibuild:
C Shapiro26fb1012017-12-14 16:38:03 -070088 test_label_cmd = 'cros_config / test-label'
89 result = host.run(command=test_label_cmd, ignore_status=True)
C Shapiro32700032017-11-03 12:46:55 -060090 if result.exit_status == 0:
91 model = result.stdout.strip()
C Shapiro26fb1012017-12-14 16:38:03 -070092 if not model:
93 mosys_cmd = 'mosys platform model'
94 result = host.run(command=mosys_cmd, ignore_status=True)
95 if result.exit_status == 0:
96 model = result.stdout.strip()
C Shapiro32700032017-11-03 12:46:55 -060097
98 # We need some sort of backwards compatibility for boards that
99 # are not yet supported with mosys and unified builds.
100 # This is necessary so that we can begin changing cbuildbot to take
101 # advantage of the model/board label differentiations for
102 # scheduling, while still retaining backwards compatibility.
103 return [model or lsb_output.board]
C Shapirob05c00b2017-07-18 15:06:49 -0600104
105
C Shapirobe0ff8d2019-06-14 10:41:43 -0600106class DeviceSkuLabel(base_label.StringPrefixLabel):
107 """Determine the correct device_sku label for the device."""
108
109 _NAME = ds_constants.DEVICE_SKU_LABEL
110
111 def generate_labels(self, host):
112 device_sku = host.host_info_store.get().device_sku
113 if device_sku:
114 return [device_sku]
115
116 mosys_cmd = 'mosys platform sku'
117 result = host.run(command=mosys_cmd, ignore_status=True)
118 if result.exit_status == 0:
119 return [result.stdout.strip()]
120
121 return []
122
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800123 def update_for_task(self, task_name):
124 # This label is stored in the lab config, so only deploy tasks update it
125 # or when no task name is mentioned.
126 return task_name in (DEPLOY_TASK_NAME, '')
127
C Shapirobe0ff8d2019-06-14 10:41:43 -0600128
Ned Nguyene0a619d2019-07-01 15:50:23 -0600129class BrandCodeLabel(base_label.StringPrefixLabel):
130 """Determine the correct brand_code (aka RLZ-code) for the device."""
131
132 _NAME = ds_constants.BRAND_CODE_LABEL
133
134 def generate_labels(self, host):
135 brand_code = host.host_info_store.get().brand_code
136 if brand_code:
137 return [brand_code]
138
Greg Edelston7cea0c42019-11-26 15:17:22 -0700139 cros_config_cmd = 'cros_config / brand-code'
140 result = host.run(command=cros_config_cmd, ignore_status=True)
Ned Nguyene0a619d2019-07-01 15:50:23 -0600141 if result.exit_status == 0:
142 return [result.stdout.strip()]
143
144 return []
145
146
Kevin Chenga2619dc2016-03-28 11:42:08 -0700147class BluetoothLabel(base_label.BaseLabel):
148 """Label indicating if bluetooth is detected."""
149
150 _NAME = 'bluetooth'
151
152 def exists(self, host):
C Shapirobcd9c862019-05-22 17:42:08 -0600153 # Based on crbug.com/966219, the label is flipping sometimes.
154 # Potentially this is caused by testing itself.
155 # Making this label permanently sticky.
156 info = host.host_info_store.get()
157 for label in info.labels:
158 if label.startswith(self._NAME):
159 return True
160
Kevin Chenga2619dc2016-03-28 11:42:08 -0700161 result = host.run('test -d /sys/class/bluetooth/hci0',
162 ignore_status=True)
163
164 return result.exit_status == 0
165
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800166class BluetoothPeerLabel(base_label.StringPrefixLabel):
167 """Return the Bluetooth peer labels.
168
169 working_bluetooth_btpeer label is applied if a Raspberry Pi Bluetooth peer
170 is detected.There can be up to 4 Bluetooth peers. Labels
171 working_bluetooth_btpeer:[1-4] will be assigned depending on the number of
172 peers present.
173
174 """
175
176 _NAME = 'working_bluetooth_btpeer'
177
178 def exists(self, host):
179 return len(host._btpeer_host_list) > 0
180
181 def generate_labels(self, host):
182 labels_list = []
183 count = 1
184
185 for (btpeer, btpeer_host) in \
186 zip(host.btpeer_list, host._btpeer_host_list):
187 try:
188 # Initialize one device type to make sure the peer is working
189 bt_hid_device = btpeer.get_bluetooth_hid_mouse()
190 if bt_hid_device.CheckSerialConnection():
191 labels_list.append(str(count))
192 count += 1
193 except Exception as e:
194 logging.error('Error with initializing bt_hid_mouse on '
195 'btpeer %s %s', btpeer_host.hostname, e)
196
197 logging.info('Bluetooth Peer labels are %s', labels_list)
198 return labels_list
199
200 def update_for_task(self, task_name):
Shijin Abraham76bc1db2020-03-06 10:52:10 -0800201 # This label is stored in the state config, so only repair tasks update
202 # it or when no task name is mentioned.
203 return task_name in (REPAIR_TASK_NAME, '')
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800204
Kevin Chenga2619dc2016-03-28 11:42:08 -0700205
206class ECLabel(base_label.BaseLabel):
207 """Label to determine the type of EC on this host."""
208
209 _NAME = 'ec:cros'
210
211 def exists(self, host):
212 cmd = 'mosys ec info'
213 # The output should look like these, so that the last field should
214 # match our EC version scheme:
215 #
216 # stm | stm32f100 | snow_v1.3.139-375eb9f
217 # ti | Unknown-10de | peppy_v1.5.114-5d52788
218 #
219 # Non-Chrome OS ECs will look like these:
220 #
221 # ENE | KB932 | 00BE107A00
222 # ite | it8518 | 3.08
223 #
224 # And some systems don't have ECs at all (Lumpy, for example).
225 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
226
227 ecinfo = host.run(command=cmd, ignore_status=True)
228 if ecinfo.exit_status == 0:
229 res = re.search(regexp, ecinfo.stdout)
230 if res:
231 logging.info("EC version is %s", res.groups()[0])
232 return True
233 logging.info("%s got: %s", cmd, ecinfo.stdout)
234 # Has an EC, but it's not a Chrome OS EC
235 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
236 return False
237
238
Mary Ruthven935ebad2018-06-13 16:13:20 -0700239class Cr50Label(base_label.StringPrefixLabel):
Mary Ruthven6c462642019-09-17 19:13:36 -0700240 """Label indicating the cr50 image type."""
Mary Ruthven935ebad2018-06-13 16:13:20 -0700241
242 _NAME = 'cr50'
243
244 def __init__(self):
245 self.ver = None
246
Mary Ruthven935ebad2018-06-13 16:13:20 -0700247 def exists(self, host):
248 # Make sure the gsctool version command runs ok
249 self.ver = host.run('gsctool -a -f', ignore_status=True)
250 return self.ver.exit_status == 0
251
Mary Ruthven6c462642019-09-17 19:13:36 -0700252 def _get_version(self, region):
253 """Get the version number of the given region"""
254 return re.search(region + ' (\d+\.\d+\.\d+)', self.ver.stdout).group(1)
Mary Ruthven935ebad2018-06-13 16:13:20 -0700255
256 def generate_labels(self, host):
257 # Check the major version to determine prePVT vs PVT
Mary Ruthven6c462642019-09-17 19:13:36 -0700258 version = self._get_version('RW')
259 major_version = int(version.split('.')[1])
Mary Ruthven935ebad2018-06-13 16:13:20 -0700260 # PVT images have a odd major version prePVT have even
Mary Ruthven6c462642019-09-17 19:13:36 -0700261 return ['pvt' if (major_version % 2) else 'prepvt']
262
Xixuan Wu61b2b262020-03-06 10:09:55 -0800263 def update_for_task(self, task_name):
264 # This label is stored in the state config, so only repair tasks update
265 # it or when no task name is mentioned.
266 return task_name in (REPAIR_TASK_NAME, '')
267
Mary Ruthven6c462642019-09-17 19:13:36 -0700268
269class Cr50RWKeyidLabel(Cr50Label):
270 """Label indicating the cr50 RW version."""
271 _REGION = 'RW'
272 _NAME = 'cr50-rw-keyid'
273
274 def _get_keyid_info(self, region):
275 """Get the keyid of the given region."""
276 match = re.search('keyids:.*%s (\S+)' % region, self.ver.stdout)
277 keyid = match.group(1).rstrip(',')
278 is_prod = int(keyid, 16) & (1 << 2)
279 return [keyid, 'prod' if is_prod else 'dev']
280
281 def generate_labels(self, host):
282 """Get the key type."""
283 return self._get_keyid_info(self._REGION)
284
285
286class Cr50ROKeyidLabel(Cr50RWKeyidLabel):
287 """Label indicating the RO key type."""
288 _REGION = 'RO'
289 _NAME = 'cr50-ro-keyid'
290
291
292class Cr50RWVersionLabel(Cr50Label):
293 """Label indicating the cr50 RW version."""
294 _REGION = 'RW'
295 _NAME = 'cr50-rw-version'
296
297 def generate_labels(self, host):
298 """Get the version and key type"""
299 return [self._get_version(self._REGION)]
300
301
302class Cr50ROVersionLabel(Cr50RWVersionLabel):
303 """Label indicating the RO version."""
304 _REGION = 'RO'
305 _NAME = 'cr50-ro-version'
Mary Ruthven935ebad2018-06-13 16:13:20 -0700306
307
Kevin Chenga2619dc2016-03-28 11:42:08 -0700308class AccelsLabel(base_label.BaseLabel):
309 """Determine the type of accelerometers on this host."""
310
311 _NAME = 'accel:cros-ec'
312
313 def exists(self, host):
314 # Check to make sure we have ectool
315 rv = host.run('which ectool', ignore_status=True)
316 if rv.exit_status:
317 logging.info("No ectool cmd found; assuming no EC accelerometers")
318 return False
319
320 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700321 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700322 if rv.exit_status:
323 logging.info("EC does not support motionsense command; "
324 "assuming no EC accelerometers")
325 return False
326
327 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700328 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700329 if active[0] == "0":
330 logging.info("Motion sense inactive; assuming no EC accelerometers")
331 return False
332
333 logging.info("EC accelerometers found")
334 return True
335
336
337class ChameleonLabel(base_label.BaseLabel):
338 """Determine if a Chameleon is connected to this host."""
339
340 _NAME = 'chameleon'
341
342 def exists(self, host):
Xixuan Wu7afb54f2019-09-17 11:45:20 -0700343 # See crbug.com/1004500#2 for details.
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800344 has_chameleon = host._chameleon_host is not None
Gregory Nisbetb2f6d792019-09-11 14:30:47 -0700345 # TODO(crbug.com/995900) -- debug why chameleon label is flipping
346 try:
347 logging.info("has_chameleon %s", has_chameleon)
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800348 logging.info("_chameleon_host %s",
349 getattr(host, "_chameleon_host", "NO_ATTRIBUTE"))
350 logging.info("chameleon %s",
351 getattr(host, "chameleon", "NO_ATTRIBUTE"))
Gregory Nisbetb2f6d792019-09-11 14:30:47 -0700352 except:
353 pass
354 return has_chameleon
355
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800356 def update_for_task(self, task_name):
357 # This label is stored in the state config, so only repair tasks update
358 # it or when no task name is mentioned.
359 return task_name in (REPAIR_TASK_NAME, '')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700360
361
362class ChameleonConnectionLabel(base_label.StringPrefixLabel):
363 """Return the Chameleon connection label."""
364
365 _NAME = 'chameleon'
366
367 def exists(self, host):
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800368 return host._chameleon_host is not None
Joseph Hwangeac44312016-08-31 12:08:38 +0800369
Kevin Chenga2619dc2016-03-28 11:42:08 -0700370 def generate_labels(self, host):
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800371 return [host.chameleon.get_label()]
Kevin Chenga2619dc2016-03-28 11:42:08 -0700372
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800373 def update_for_task(self, task_name):
374 # This label is stored in the lab config, so only deploy tasks update it
375 # or when no task name is mentioned.
376 return task_name in (DEPLOY_TASK_NAME, '')
377
Kevin Chenga2619dc2016-03-28 11:42:08 -0700378
Joseph Hwangeac44312016-08-31 12:08:38 +0800379class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
380 """Return the Chameleon peripherals labels.
381
382 The 'chameleon:bt_hid' label is applied if the bluetooth
383 classic hid device, i.e, RN-42 emulation kit, is detected.
384
385 Any peripherals plugged into the chameleon board would be
386 detected and applied proper labels in this class.
387 """
388
389 _NAME = 'chameleon'
390
391 def exists(self, host):
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800392 return host._chameleon_host is not None
Joseph Hwangeac44312016-08-31 12:08:38 +0800393
394 def generate_labels(self, host):
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800395 labels = []
396 try:
397 bt_hid_device = host.chameleon.get_bluetooth_hid_mouse()
398 if bt_hid_device.CheckSerialConnection():
399 labels.append('bt_hid')
400 except:
401 logging.error('Error with initializing bt_hid_mouse')
howardchung83e55272019-08-08 14:08:05 +0800402
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800403 try:
404 ble_hid_device = host.chameleon.get_ble_mouse()
405 if ble_hid_device.CheckSerialConnection():
406 labels.append('bt_ble_hid')
407 except:
408 logging.error('Error with initializing ble_hid_mouse')
howardchung83e55272019-08-08 14:08:05 +0800409
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800410 try:
411 bt_a2dp_sink = host.chameleon.get_bluetooth_a2dp_sink()
412 if bt_a2dp_sink.CheckSerialConnection():
413 labels.append('bt_a2dp_sink')
414 except:
415 logging.error('Error with initializing bt_a2dp_sink')
howardchung83e55272019-08-08 14:08:05 +0800416
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800417 try:
418 bt_base_device = host.chameleon.get_bluetooth_base()
419 if bt_base_device.IsDetected():
420 labels.append('bt_base')
421 except:
422 logging.error('Error in detecting bt_base')
howardchung83e55272019-08-08 14:08:05 +0800423
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800424 if labels != []:
425 labels.append('bt_peer')
Joseph Hwang89e779c2019-12-24 16:05:56 +0800426
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800427 logging.info('Chameleon Bluetooth labels are %s', labels)
428 return labels
Shijin Abrahamff61ac32019-05-20 12:35:44 -0700429
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800430 def update_for_task(self, task_name):
431 # This label is stored in the lab config, so only deploy tasks update it
432 # or when no task name is mentioned.
433 return task_name in (DEPLOY_TASK_NAME, '')
Joseph Hwangeac44312016-08-31 12:08:38 +0800434
435
Kevin Chenga2619dc2016-03-28 11:42:08 -0700436class AudioLoopbackDongleLabel(base_label.BaseLabel):
437 """Return the label if an audio loopback dongle is plugged in."""
438
439 _NAME = 'audio_loopback_dongle'
440
441 def exists(self, host):
Gregory Nisbete280ea22019-08-16 17:50:03 -0700442 # Based on crbug.com/991285, AudioLoopbackDongle sometimes flips.
443 # Ensure that AudioLoopbackDongle.exists returns True
444 # forever, after it returns True *once*.
445 if self._cached_exists(host):
446 # If the current state is True, return it, don't run the command on
447 # the DUT and potentially flip the state.
448 return True
449 # If the current state is not True, run the command on
450 # the DUT. The new state will be set to whatever the command
451 # produces.
452 return self._host_run_exists(host)
453
454 def _cached_exists(self, host):
455 """Get the state of AudioLoopbackDongle in the data store"""
456 info = host.host_info_store.get()
457 for label in info.labels:
458 if label.startswith(self._NAME):
459 return True
460 return False
461
462 def _host_run_exists(self, host):
463 """Detect presence of audio_loopback_dongle by physically
464 running a command on the DUT."""
Kevin Chenga2619dc2016-03-28 11:42:08 -0700465 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
466 ignore_status=True).stdout
467 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
468 cras_utils.node_type_is_plugged('MIC', nodes_info)):
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800469 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700470 return False
471
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800472 def update_for_task(self, task_name):
473 # This label is stored in the state config, so only repair tasks update
474 # it or when no task name is mentioned.
475 return task_name in (REPAIR_TASK_NAME, '')
476
Kevin Chenga2619dc2016-03-28 11:42:08 -0700477
478class PowerSupplyLabel(base_label.StringPrefixLabel):
479 """
480 Return the label describing the power supply type.
481
482 Labels representing this host's power supply.
483 * `power:battery` when the device has a battery intended for
484 extended use
485 * `power:AC_primary` when the device has a battery not intended
486 for extended use (for moving the machine, etc)
487 * `power:AC_only` when the device has no battery at all.
488 """
489
490 _NAME = 'power'
491
492 def __init__(self):
493 self.psu_cmd_result = None
494
495
496 def exists(self, host):
497 self.psu_cmd_result = host.run(command='mosys psu type',
498 ignore_status=True)
499 return self.psu_cmd_result.stdout.strip() != 'unknown'
500
501
502 def generate_labels(self, host):
503 if self.psu_cmd_result.exit_status:
504 # The psu command for mosys is not included for all platforms. The
505 # assumption is that the device will have a battery if the command
506 # is not found.
507 return ['battery']
508 return [self.psu_cmd_result.stdout.strip()]
509
510
511class StorageLabel(base_label.StringPrefixLabel):
512 """
513 Return the label describing the storage type.
514
515 Determine if the internal device is SCSI or dw_mmc device.
516 Then check that it is SSD or HDD or eMMC or something else.
517
518 Labels representing this host's internal device type:
519 * `storage:ssd` when internal device is solid state drive
520 * `storage:hdd` when internal device is hard disk drive
521 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700522 * `storage:nvme` when internal device is NVMe drive
Alexis Savery570e7fb2018-06-26 10:48:15 -0700523 * `storage:ufs` when internal device is ufs drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700524 * None When internal device is something else or
525 when we are unable to determine the type
526 """
527
528 _NAME = 'storage'
529
530 def __init__(self):
531 self.type_str = ''
532
533
534 def exists(self, host):
535 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
536 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
537 '. /usr/share/misc/chromeos-common.sh;',
538 'load_base_vars;',
539 'get_fixed_dst_drive'])
540 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
541 if rootdev.exit_status:
542 logging.info("Fail to run %s", rootdev_cmd)
543 return False
544 rootdev_str = rootdev.stdout.strip()
545
546 if not rootdev_str:
547 return False
548
549 rootdev_base = os.path.basename(rootdev_str)
550
551 mmc_pattern = '/dev/mmcblk[0-9]'
552 if re.match(mmc_pattern, rootdev_str):
553 # Use type to determine if the internal device is eMMC or somthing
554 # else. We can assume that MMC is always an internal device.
555 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
556 type = host.run(command=type_cmd, ignore_status=True)
557 if type.exit_status:
558 logging.info("Fail to run %s", type_cmd)
559 return False
560 type_str = type.stdout.strip()
561
562 if type_str == 'MMC':
563 self.type_str = 'mmc'
564 return True
565
566 scsi_pattern = '/dev/sd[a-z]+'
567 if re.match(scsi_pattern, rootdev.stdout):
568 # Read symlink for /sys/block/sd* to determine if the internal
569 # device is connected via ata or usb.
570 link_cmd = 'readlink /sys/block/%s' % rootdev_base
571 link = host.run(command=link_cmd, ignore_status=True)
572 if link.exit_status:
573 logging.info("Fail to run %s", link_cmd)
574 return False
575 link_str = link.stdout.strip()
576 if 'usb' in link_str:
577 return False
Alexis Savery570e7fb2018-06-26 10:48:15 -0700578 elif 'ufs' in link_str:
579 self.type_str = 'ufs'
580 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700581
582 # Read rotation to determine if the internal device is ssd or hdd.
583 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
584 % rootdev_base)
585 rotate = host.run(command=rotate_cmd, ignore_status=True)
586 if rotate.exit_status:
587 logging.info("Fail to run %s", rotate_cmd)
588 return False
589 rotate_str = rotate.stdout.strip()
590
591 rotate_dict = {'0':'ssd', '1':'hdd'}
592 self.type_str = rotate_dict.get(rotate_str)
593 return True
594
Gwendal Grignou327fec62017-07-26 15:25:43 -0700595 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
596 if re.match(nvme_pattern, rootdev_str):
Gwendal Grignou3660c192017-12-06 10:11:23 -0800597 self.type_str = 'nvme'
Gwendal Grignou327fec62017-07-26 15:25:43 -0700598 return True
599
Kevin Chenga2619dc2016-03-28 11:42:08 -0700600 # All other internal device / error case will always fall here
601 return False
602
Kevin Chenga2619dc2016-03-28 11:42:08 -0700603 def generate_labels(self, host):
604 return [self.type_str]
605
606
607class ServoLabel(base_label.BaseLabel):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800608 # class will be removed as part of decommission the old servo label
609 # "not_connected" and "wrong_config" will be part of ServoHost
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800610 """
611 Label servo is applying if a servo is present.
612 Label servo_state present always.
613 """
Kevin Chenga2619dc2016-03-28 11:42:08 -0700614
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800615 _NAME_OLD = 'servo'
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800616 _NAME = servo_host.SERVO_STATE_LABEL_PREFIX
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800617
618 def get(self, host):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800619 state = self._get_state(host)
620 servo_state = self._NAME + ':' + state
621 if state == servo_host.SERVO_STATE_NOT_CONNECTED:
622 return [servo_state]
623 return [self._NAME_OLD, servo_state]
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800624
625 def get_all_labels(self):
626 return set([self._NAME]), set([self._NAME_OLD])
Kevin Chenga2619dc2016-03-28 11:42:08 -0700627
628 def exists(self, host):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800629 pass
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700630
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800631 def _get_state(self, host):
632 # Based on crbug.com/995900, Servo sometimes flips.
633 # Ensure that labels has servo or servo_state label
634 # forever, after it returns state *once*.
635 state = self._cached_servo_state_status(host)
636 if state:
637 # If status exist we do not need to run anything
638 return state
639
640 # by last changes this point should not reached now
641 # only till not all DUTs have servo_state label
642 servo_label = self._cached_servo_label(host)
643 if servo_label:
644 return servo_host.SERVO_STATE_WORKING
645 return servo_host.SERVO_STATE_NOT_CONNECTED
646
647 def _cached_servo_state_status(self, host):
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700648 """Get the state of Servo in the data store"""
649 info = host.host_info_store.get()
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800650 # First try to find targeted label
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700651 for label in info.labels:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800652 if label.startswith(self._NAME):
653 suffix_length = len(self._NAME) + 1
654 return label[suffix_length:]
655 return ''
656
657 def _cached_servo_label(self, host):
658 """Get the state of Servo in the data store"""
659 info = host.host_info_store.get()
660 # First try to find targeted label
661 for label in info.labels:
662 if label is not None and label.strip() == self._NAME_OLD:
Gregory Nisbet8b008f82019-08-20 13:06:19 -0700663 return True
664 return False
665
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800666 def update_for_task(self, task_name):
667 # This label is stored in the state config, so only repair tasks update
668 # it or when no task name is mentioned.
669 return task_name in (REPAIR_TASK_NAME, '')
670
Kevin Chenga2619dc2016-03-28 11:42:08 -0700671
Ilja H. Friedel50290642017-12-01 19:39:53 -0800672class ArcLabel(base_label.BaseLabel):
673 """Label indicates if host has ARC support."""
674
675 _NAME = 'arc'
676
677 @base_label.forever_exists_decorate
678 def exists(self, host):
679 return 0 == host.run(
680 'grep CHROMEOS_ARC_VERSION /etc/lsb-release',
681 ignore_status=True).exit_status
682
683
684class CtsArchLabel(base_label.StringLabel):
685 """Labels to determine the abi of the CTS bundle (arm or x86 only)."""
Rohit Makasana5a153502016-06-13 15:50:09 -0700686
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700687 _NAME = ['cts_abi_arm', 'cts_abi_x86', 'cts_cpu_arm', 'cts_cpu_x86']
Rohit Makasana5a153502016-06-13 15:50:09 -0700688
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700689 def _get_cts_abis(self, arch):
Rohit Makasana5a153502016-06-13 15:50:09 -0700690 """Return supported CTS ABIs.
691
692 @return List of supported CTS bundle ABIs.
693 """
694 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700695 return cts_abis.get(arch, [])
696
697 def _get_cts_cpus(self, arch):
698 """Return supported CTS native CPUs.
699
700 This is needed for CTS_Instant scheduling.
701 @return List of supported CTS native CPUs.
702 """
703 cts_cpus = {'x86_64': ['x86'], 'arm': ['arm']}
704 return cts_cpus.get(arch, [])
Rohit Makasana5a153502016-06-13 15:50:09 -0700705
Rohit Makasana5a153502016-06-13 15:50:09 -0700706 def generate_labels(self, host):
Ilja H. Friedela7502f52018-10-16 23:33:25 -0700707 cpu_arch = host.get_cpu_arch()
708 abi_labels = ['cts_abi_' + abi for abi in self._get_cts_abis(cpu_arch)]
709 cpu_labels = ['cts_cpu_' + cpu for cpu in self._get_cts_cpus(cpu_arch)]
710 return abi_labels + cpu_labels
Rohit Makasana5a153502016-06-13 15:50:09 -0700711
712
Kevin Chenga2619dc2016-03-28 11:42:08 -0700713class VideoGlitchLabel(base_label.BaseLabel):
714 """Label indicates if host supports video glitch detection tests."""
715
716 _NAME = 'video_glitch_detection'
717
718 def exists(self, host):
719 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
720
721 return board in video_test_constants.SUPPORTED_BOARDS
722
723
Kevin Chenga2619dc2016-03-28 11:42:08 -0700724class InternalDisplayLabel(base_label.StringLabel):
725 """Label that determines if the device has an internal display."""
726
727 _NAME = 'internal_display'
728
729 def generate_labels(self, host):
730 from autotest_lib.client.cros.graphics import graphics_utils
731 from autotest_lib.client.common_lib import utils as common_utils
732
733 def __system_output(cmd):
734 return host.run(cmd).stdout
735
736 def __read_file(remote_path):
737 return host.run('cat %s' % remote_path).stdout
738
739 # Hijack the necessary client functions so that we can take advantage
740 # of the client lib here.
741 # FIXME: find a less hacky way than this
742 original_system_output = utils.system_output
743 original_read_file = common_utils.read_file
744 utils.system_output = __system_output
745 common_utils.read_file = __read_file
746 try:
747 return ([self._NAME]
748 if graphics_utils.has_internal_display()
749 else [])
750 finally:
751 utils.system_output = original_system_output
752 common_utils.read_file = original_read_file
753
754
755class LucidSleepLabel(base_label.BaseLabel):
756 """Label that determines if device has support for lucid sleep."""
757
758 # TODO(kevcheng): See if we can determine if this label is applicable a
759 # better way (crbug.com/592146).
760 _NAME = 'lucidsleep'
RaviChandra Sadinenic06b00e2018-11-03 09:56:11 -0700761 LUCID_SLEEP_BOARDS = ['nocturne', 'poppy']
Kevin Chenga2619dc2016-03-28 11:42:08 -0700762
763 def exists(self, host):
764 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
765 return board in self.LUCID_SLEEP_BOARDS
766
767
Xixuan Wu78569d02019-09-15 16:08:25 -0700768def _parse_hwid_labels(hwid_info_list):
769 if len(hwid_info_list) == 0:
770 return hwid_info_list
771
772 res = []
773 # See crbug.com/997816#c7 for details of two potential formats of returns
774 # from HWID server.
775 if isinstance(hwid_info_list[0], dict):
776 # Format of hwid_info:
777 # [{u'name': u'sku', u'value': u'xxx'}, ..., ]
778 for hwid_info in hwid_info_list:
779 value = hwid_info.get('value', '')
780 name = hwid_info.get('name', '')
781 # There should always be a name but just in case there is not.
782 if name:
783 new_label = name if not value else '%s:%s' % (name, value)
784 res.append(new_label)
785 else:
786 # Format of hwid_info:
787 # [<DUTLabel name: 'sku' value: u'xxx'>, ..., ]
788 for hwid_info in hwid_info_list:
789 new_label = str(hwid_info)
790 logging.info('processing hwid label: %s', new_label)
791 res.append(new_label)
792
793 return res
794
795
Kevin Cheng80ad5732016-03-31 16:01:56 -0700796class HWIDLabel(base_label.StringLabel):
797 """Return all the labels generated from the hwid."""
798
799 # We leave out _NAME because hwid_lib will generate everything for us.
800
801 def __init__(self):
802 # Grab the key file needed to access the hwid service.
803 self.key_file = global_config.global_config.get_config_value(
804 'CROS', 'HWID_KEY', type=str)
805
806
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700807 @staticmethod
808 def _merge_hwid_label_lists(new, old):
809 """merge a list of old and new values for hwid_labels.
810 preferring new values if available
811
812 @returns: list of labels"""
813 # TODO(gregorynisbet): what is the appropriate way to merge
814 # old and new information?
815 retained = set(x for x in old)
816 for label in new:
817 key, sep, value = label.partition(':')
818 # If we have a key-value key such as variant:aaa,
819 # then we remove all the old labels with the same key.
820 if sep:
821 retained = set(x for x in retained if (not x.startswith(key + ':')))
822 return list(sorted(retained.union(new)))
823
824
825 def _hwid_label_names(self):
826 """get the labels that hwid_lib controls.
827
828 @returns: hwid_labels
829 """
830 all_hwid_labels, _ = self.get_all_labels()
831 # If and only if get_all_labels was unsuccessful,
832 # it will return a falsey value.
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700833 out = all_hwid_labels or HWID_LABELS_FALLBACK
834
835 # TODO(gregorynisbet): remove this
836 # TODO(crbug.com/999785)
837 if "sku" not in out:
838 logging.info("sku-less label names %s", out)
839
840 return out
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700841
842
843 def _old_label_values(self, host):
844 """get the hwid_lib labels on previous run
845
846 @returns: hwid_labels"""
847 out = []
848 info = host.host_info_store.get()
849 for hwid_label in self._hwid_label_names():
850 for label in info.labels:
851 # NOTE: we want *all* the labels starting
852 # with this prefix.
853 if label.startswith(hwid_label):
854 out.append(label)
855 return out
856
857
Kevin Cheng80ad5732016-03-31 16:01:56 -0700858 def generate_labels(self, host):
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700859 # use previous values as default
860 old_hwid_labels = self._old_label_values(host)
Xixuan Wue63f8352019-09-13 15:18:03 -0700861 logging.info("old_hwid_labels: %r", old_hwid_labels)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700862 hwid = host.run_output('crossystem hwid').strip()
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700863 hwid_info_list = []
864 try:
865 hwid_info_response = hwid_lib.get_hwid_info(
866 hwid=hwid,
867 info_type=hwid_lib.HWID_INFO_LABEL,
868 key_file=self.key_file,
869 )
Xixuan Wue63f8352019-09-13 15:18:03 -0700870 logging.info("hwid_info_response: %r", hwid_info_response)
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700871 hwid_info_list = hwid_info_response.get('labels', [])
872 except hwid_lib.HwIdException as e:
873 logging.info("HwIdException: %s", e)
Kevin Cheng80ad5732016-03-31 16:01:56 -0700874
Xixuan Wu78569d02019-09-15 16:08:25 -0700875 new_hwid_labels = _parse_hwid_labels(hwid_info_list)
876 logging.info("new HWID labels: %r", new_hwid_labels)
Gregory Nisbetbfd120f2019-09-04 14:49:46 -0700877
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700878 return HWIDLabel._merge_hwid_label_lists(
879 old=old_hwid_labels,
Xixuan Wu78569d02019-09-15 16:08:25 -0700880 new=new_hwid_labels,
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -0700881 )
Kevin Cheng80ad5732016-03-31 16:01:56 -0700882
883
884 def get_all_labels(self):
885 """We need to try all labels as a prefix and as standalone.
886
887 We don't know for sure which labels are prefix labels and which are
888 standalone so we try all of them as both.
889 """
890 all_hwid_labels = []
891 try:
892 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
893 self.key_file)
894 except IOError:
895 logging.error('Can not open key file: %s', self.key_file)
896 except hwid_lib.HwIdException as e:
897 logging.error('hwid service: %s', e)
898 return all_hwid_labels, all_hwid_labels
899
900
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800901class DetachableBaseLabel(base_label.BaseLabel):
902 """Label indicating if device has detachable keyboard."""
903
904 _NAME = 'detachablebase'
905
906 def exists(self, host):
907 return host.run('which hammerd', ignore_status=True).exit_status == 0
908
909
Tom Hughese9552342018-12-18 14:29:25 -0800910class FingerprintLabel(base_label.BaseLabel):
911 """Label indicating whether device has fingerprint sensor."""
912
913 _NAME = 'fingerprint'
914
915 def exists(self, host):
916 return host.run('test -c /dev/cros_fp',
917 ignore_status=True).exit_status == 0
918
919
Garry Wang17a829e2019-03-20 12:03:18 -0700920class ReferenceDesignLabel(base_label.StringPrefixLabel):
921 """Determine the correct reference design label for the device. """
922
923 _NAME = 'reference_design'
924
925 def __init__(self):
926 self.response = None
927
928 def exists(self, host):
929 self.response = host.run('mosys platform family', ignore_status=True)
930 return self.response.exit_status == 0
931
932 def generate_labels(self, host):
933 if self.exists(host):
934 return [self.response.stdout.strip()]
935
936
Kevin Chenga2619dc2016-03-28 11:42:08 -0700937CROS_LABELS = [
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800938 AudioLoopbackDongleLabel(), #STATECONFIG
Shijin Abraham76bc1db2020-03-06 10:52:10 -0800939 BluetoothPeerLabel(), #STATECONFIG
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800940 ChameleonConnectionLabel(), #LABCONFIG
941 ChameleonLabel(), #STATECONFIG
942 ChameleonPeripheralsLabel(), #LABCONFIG
Kevin Chenga2619dc2016-03-28 11:42:08 -0700943 common_label.OSLabel(),
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800944 DeviceSkuLabel(), #LABCONFIG
Kevin Cheng80ad5732016-03-31 16:01:56 -0700945 HWIDLabel(),
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800946 ServoLabel(), #STATECONFIG
Xixuan Wu457b4ac2020-03-02 14:39:08 -0800947 # Temporarily add back as there's no way to reference cr50 configs.
948 # See crbug.com/1057145 for the root cause.
949 # See crbug.com/1057719 for future tracking.
950 Cr50Label(),
951 Cr50ROKeyidLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700952]
Garry Wange4b6d6e2019-06-17 17:08:46 -0700953
954LABSTATION_LABELS = [
Garry Wange4b6d6e2019-06-17 17:08:46 -0700955 common_label.OSLabel(),
956]