blob: ef52f39d135f880c60b040e4cb396890b9f682ec [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
7import logging
8import os
9import re
10
11import common
12
13from autotest_lib.client.bin import utils
Kevin Cheng80ad5732016-03-31 16:01:56 -070014from autotest_lib.client.common_lib import global_config
Kevin Chenga2619dc2016-03-28 11:42:08 -070015from autotest_lib.client.cros.audio import cras_utils
Kevin Chenga2619dc2016-03-28 11:42:08 -070016from autotest_lib.client.cros.video import constants as video_test_constants
17from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
18from autotest_lib.server.hosts import base_label
19from autotest_lib.server.hosts import common_label
Kevin Chengd9dfa582016-05-04 09:37:34 -070020from autotest_lib.server.hosts import servo_host
Kevin Cheng80ad5732016-03-31 16:01:56 -070021from autotest_lib.site_utils import hwid_lib
Kevin Chenga2619dc2016-03-28 11:42:08 -070022
23# pylint: disable=missing-docstring
24
Kevin Chenga8455302016-08-31 20:54:41 +000025class BoardLabel(base_label.StringPrefixLabel):
26 """Determine the correct board label for the device."""
27
28 _NAME = ds_constants.BOARD_PREFIX.rstrip(':')
29
30 def generate_labels(self, host):
31 # We only want to apply the board labels once, which is when they get
32 # added to the AFE. That way we don't have to worry about the board
33 # label switching on us if the wrong builds get put on the devices.
34 # crbug.com/624207 records one event of the board label switching
35 # unexpectedly on us.
36 for label in host._afe_host.labels:
37 if label.startswith(self._NAME + ':'):
38 return [label.split(':')[-1]]
39
40 # TODO(kevcheng): for now this will dup the code in CrosHost and a
41 # separate cl will refactor the get_board in CrosHost to just return the
42 # board without the BOARD_PREFIX and all the other callers will be
43 # updated to not need to clear it out and this code will be replaced to
44 # just call the host's get_board() method.
45 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
46 run_method=host.run)
47 return [release_info['CHROMEOS_RELEASE_BOARD']]
48
49
C Shapirob05c00b2017-07-18 15:06:49 -060050class ModelLabel(base_label.StringPrefixLabel):
51 """Determine the correct model label for the device."""
52
53 _NAME = ds_constants.MODEL_LABEL
54
55 def generate_labels(self, host):
56 # Return the existing label if set to defend against any bad image
57 # pushes to the host. See comment in BoardLabel for more details.
58 for label in host._afe_host.labels:
59 if label.startswith(self._NAME + ':'):
60 return [label.split(':')[-1]]
61
62 cmd = "mosys platform model"
63 result = host.run(command=cmd, ignore_status=True)
64 if result.exit_status == 0:
65 return result.stddout
66 else:
67 logging.info("%s exited with status %d", cmd, result.exit_status)
68 return ""
69
70
Kevin Chenga2619dc2016-03-28 11:42:08 -070071class LightSensorLabel(base_label.BaseLabel):
72 """Label indicating if a light sensor is detected."""
73
74 _NAME = 'lightsensor'
75 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
76 _LIGHTSENSOR_FILES = [
77 "in_illuminance0_input",
78 "in_illuminance_input",
79 "in_illuminance0_raw",
80 "in_illuminance_raw",
81 "illuminance0_input",
82 ]
83
84 def exists(self, host):
85 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
86 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
87 # Run the search cmd following the symlinks. Stderr_tee is set to
88 # None as there can be a symlink loop, but the command will still
89 # execute correctly with a few messages printed to stderr.
90 result = host.run(search_cmd, stdout_tee=None, stderr_tee=None,
91 ignore_status=True)
92
93 return result.exit_status == 0
94
95
96class BluetoothLabel(base_label.BaseLabel):
97 """Label indicating if bluetooth is detected."""
98
99 _NAME = 'bluetooth'
100
101 def exists(self, host):
102 result = host.run('test -d /sys/class/bluetooth/hci0',
103 ignore_status=True)
104
105 return result.exit_status == 0
106
107
108class ECLabel(base_label.BaseLabel):
109 """Label to determine the type of EC on this host."""
110
111 _NAME = 'ec:cros'
112
113 def exists(self, host):
114 cmd = 'mosys ec info'
115 # The output should look like these, so that the last field should
116 # match our EC version scheme:
117 #
118 # stm | stm32f100 | snow_v1.3.139-375eb9f
119 # ti | Unknown-10de | peppy_v1.5.114-5d52788
120 #
121 # Non-Chrome OS ECs will look like these:
122 #
123 # ENE | KB932 | 00BE107A00
124 # ite | it8518 | 3.08
125 #
126 # And some systems don't have ECs at all (Lumpy, for example).
127 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
128
129 ecinfo = host.run(command=cmd, ignore_status=True)
130 if ecinfo.exit_status == 0:
131 res = re.search(regexp, ecinfo.stdout)
132 if res:
133 logging.info("EC version is %s", res.groups()[0])
134 return True
135 logging.info("%s got: %s", cmd, ecinfo.stdout)
136 # Has an EC, but it's not a Chrome OS EC
137 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
138 return False
139
140
141class AccelsLabel(base_label.BaseLabel):
142 """Determine the type of accelerometers on this host."""
143
144 _NAME = 'accel:cros-ec'
145
146 def exists(self, host):
147 # Check to make sure we have ectool
148 rv = host.run('which ectool', ignore_status=True)
149 if rv.exit_status:
150 logging.info("No ectool cmd found; assuming no EC accelerometers")
151 return False
152
153 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700154 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700155 if rv.exit_status:
156 logging.info("EC does not support motionsense command; "
157 "assuming no EC accelerometers")
158 return False
159
160 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700161 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700162 if active[0] == "0":
163 logging.info("Motion sense inactive; assuming no EC accelerometers")
164 return False
165
166 logging.info("EC accelerometers found")
167 return True
168
169
170class ChameleonLabel(base_label.BaseLabel):
171 """Determine if a Chameleon is connected to this host."""
172
173 _NAME = 'chameleon'
174
175 def exists(self, host):
176 return host._chameleon_host is not None
177
178
179class ChameleonConnectionLabel(base_label.StringPrefixLabel):
180 """Return the Chameleon connection label."""
181
182 _NAME = 'chameleon'
183
184 def exists(self, host):
185 return host._chameleon_host is not None
186
Joseph Hwangeac44312016-08-31 12:08:38 +0800187
Kevin Chenga2619dc2016-03-28 11:42:08 -0700188 def generate_labels(self, host):
189 return [host.chameleon.get_label()]
190
191
Joseph Hwangeac44312016-08-31 12:08:38 +0800192class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
193 """Return the Chameleon peripherals labels.
194
195 The 'chameleon:bt_hid' label is applied if the bluetooth
196 classic hid device, i.e, RN-42 emulation kit, is detected.
197
198 Any peripherals plugged into the chameleon board would be
199 detected and applied proper labels in this class.
200 """
201
202 _NAME = 'chameleon'
203
204 def exists(self, host):
205 return host._chameleon_host is not None
206
207
208 def generate_labels(self, host):
209 bt_hid_device = host.chameleon.get_bluetooh_hid_mouse()
210 return ['bt_hid'] if bt_hid_device.CheckSerialConnection() else []
211
212
Kevin Chenga2619dc2016-03-28 11:42:08 -0700213class AudioLoopbackDongleLabel(base_label.BaseLabel):
214 """Return the label if an audio loopback dongle is plugged in."""
215
216 _NAME = 'audio_loopback_dongle'
217
218 def exists(self, host):
219 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
220 ignore_status=True).stdout
221 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
222 cras_utils.node_type_is_plugged('MIC', nodes_info)):
223 return True
224 return False
225
226
227class PowerSupplyLabel(base_label.StringPrefixLabel):
228 """
229 Return the label describing the power supply type.
230
231 Labels representing this host's power supply.
232 * `power:battery` when the device has a battery intended for
233 extended use
234 * `power:AC_primary` when the device has a battery not intended
235 for extended use (for moving the machine, etc)
236 * `power:AC_only` when the device has no battery at all.
237 """
238
239 _NAME = 'power'
240
241 def __init__(self):
242 self.psu_cmd_result = None
243
244
245 def exists(self, host):
246 self.psu_cmd_result = host.run(command='mosys psu type',
247 ignore_status=True)
248 return self.psu_cmd_result.stdout.strip() != 'unknown'
249
250
251 def generate_labels(self, host):
252 if self.psu_cmd_result.exit_status:
253 # The psu command for mosys is not included for all platforms. The
254 # assumption is that the device will have a battery if the command
255 # is not found.
256 return ['battery']
257 return [self.psu_cmd_result.stdout.strip()]
258
259
260class StorageLabel(base_label.StringPrefixLabel):
261 """
262 Return the label describing the storage type.
263
264 Determine if the internal device is SCSI or dw_mmc device.
265 Then check that it is SSD or HDD or eMMC or something else.
266
267 Labels representing this host's internal device type:
268 * `storage:ssd` when internal device is solid state drive
269 * `storage:hdd` when internal device is hard disk drive
270 * `storage:mmc` when internal device is mmc drive
271 * None When internal device is something else or
272 when we are unable to determine the type
273 """
274
275 _NAME = 'storage'
276
277 def __init__(self):
278 self.type_str = ''
279
280
281 def exists(self, host):
282 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
283 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
284 '. /usr/share/misc/chromeos-common.sh;',
285 'load_base_vars;',
286 'get_fixed_dst_drive'])
287 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
288 if rootdev.exit_status:
289 logging.info("Fail to run %s", rootdev_cmd)
290 return False
291 rootdev_str = rootdev.stdout.strip()
292
293 if not rootdev_str:
294 return False
295
296 rootdev_base = os.path.basename(rootdev_str)
297
298 mmc_pattern = '/dev/mmcblk[0-9]'
299 if re.match(mmc_pattern, rootdev_str):
300 # Use type to determine if the internal device is eMMC or somthing
301 # else. We can assume that MMC is always an internal device.
302 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
303 type = host.run(command=type_cmd, ignore_status=True)
304 if type.exit_status:
305 logging.info("Fail to run %s", type_cmd)
306 return False
307 type_str = type.stdout.strip()
308
309 if type_str == 'MMC':
310 self.type_str = 'mmc'
311 return True
312
313 scsi_pattern = '/dev/sd[a-z]+'
314 if re.match(scsi_pattern, rootdev.stdout):
315 # Read symlink for /sys/block/sd* to determine if the internal
316 # device is connected via ata or usb.
317 link_cmd = 'readlink /sys/block/%s' % rootdev_base
318 link = host.run(command=link_cmd, ignore_status=True)
319 if link.exit_status:
320 logging.info("Fail to run %s", link_cmd)
321 return False
322 link_str = link.stdout.strip()
323 if 'usb' in link_str:
324 return False
325
326 # Read rotation to determine if the internal device is ssd or hdd.
327 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
328 % rootdev_base)
329 rotate = host.run(command=rotate_cmd, ignore_status=True)
330 if rotate.exit_status:
331 logging.info("Fail to run %s", rotate_cmd)
332 return False
333 rotate_str = rotate.stdout.strip()
334
335 rotate_dict = {'0':'ssd', '1':'hdd'}
336 self.type_str = rotate_dict.get(rotate_str)
337 return True
338
339 # All other internal device / error case will always fall here
340 return False
341
342
343 def generate_labels(self, host):
344 return [self.type_str]
345
346
347class ServoLabel(base_label.BaseLabel):
348 """Label to apply if a servo is present."""
349
350 _NAME = 'servo'
351
352 def exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700353 """
354 Check if the servo label should apply to the host or not.
355
356 @returns True if a servo host is detected, False otherwise.
357 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700358 servo_host_hostname = None
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700359 servo_args, _ = servo_host._get_standard_servo_args(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700360 if servo_args:
361 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700362 return (servo_host_hostname is not None
363 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700364
365
366class VideoLabel(base_label.StringLabel):
367 """Labels detailing video capabilities."""
368
369 # List gathered from
370 # https://chromium.googlesource.com/chromiumos/
371 # platform2/+/master/avtest_label_detect/main.c#19
372 _NAME = [
373 'hw_jpeg_acc_dec',
374 'hw_video_acc_h264',
375 'hw_video_acc_vp8',
376 'hw_video_acc_vp9',
377 'hw_video_acc_enc_h264',
378 'hw_video_acc_enc_vp8',
379 'webcam',
380 ]
381
382 def generate_labels(self, host):
383 result = host.run('/usr/local/bin/avtest_label_detect',
384 ignore_status=True).stdout
385 return re.findall('^Detected label: (\w+)$', result, re.M)
386
387
Rohit Makasana5a153502016-06-13 15:50:09 -0700388class CTSArchLabel(base_label.StringLabel):
389 """Labels to determine CTS abi."""
390
391 _NAME = ['cts_abi_arm', 'cts_abi_x86']
392
393 def _get_cts_abis(self, host):
394 """Return supported CTS ABIs.
395
396 @return List of supported CTS bundle ABIs.
397 """
398 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
399 return cts_abis.get(host.get_cpu_arch(), [])
400
401
402 def generate_labels(self, host):
403 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
404
405
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700406class ArcLabel(base_label.BaseLabel):
407 """Label indicates if host has ARC support."""
408
409 _NAME = 'arc'
410
Kevin Chengeee38e02016-08-30 16:13:23 -0700411 @base_label.forever_exists_decorate
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700412 def exists(self, host):
413 return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
414 ignore_status=True).exit_status
415
416
Kevin Chenga2619dc2016-03-28 11:42:08 -0700417class VideoGlitchLabel(base_label.BaseLabel):
418 """Label indicates if host supports video glitch detection tests."""
419
420 _NAME = 'video_glitch_detection'
421
422 def exists(self, host):
423 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
424
425 return board in video_test_constants.SUPPORTED_BOARDS
426
427
Kevin Chenga2619dc2016-03-28 11:42:08 -0700428class InternalDisplayLabel(base_label.StringLabel):
429 """Label that determines if the device has an internal display."""
430
431 _NAME = 'internal_display'
432
433 def generate_labels(self, host):
434 from autotest_lib.client.cros.graphics import graphics_utils
435 from autotest_lib.client.common_lib import utils as common_utils
436
437 def __system_output(cmd):
438 return host.run(cmd).stdout
439
440 def __read_file(remote_path):
441 return host.run('cat %s' % remote_path).stdout
442
443 # Hijack the necessary client functions so that we can take advantage
444 # of the client lib here.
445 # FIXME: find a less hacky way than this
446 original_system_output = utils.system_output
447 original_read_file = common_utils.read_file
448 utils.system_output = __system_output
449 common_utils.read_file = __read_file
450 try:
451 return ([self._NAME]
452 if graphics_utils.has_internal_display()
453 else [])
454 finally:
455 utils.system_output = original_system_output
456 common_utils.read_file = original_read_file
457
458
459class LucidSleepLabel(base_label.BaseLabel):
460 """Label that determines if device has support for lucid sleep."""
461
462 # TODO(kevcheng): See if we can determine if this label is applicable a
463 # better way (crbug.com/592146).
464 _NAME = 'lucidsleep'
465 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
466
467 def exists(self, host):
468 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
469 return board in self.LUCID_SLEEP_BOARDS
470
471
Kevin Cheng80ad5732016-03-31 16:01:56 -0700472class HWIDLabel(base_label.StringLabel):
473 """Return all the labels generated from the hwid."""
474
475 # We leave out _NAME because hwid_lib will generate everything for us.
476
477 def __init__(self):
478 # Grab the key file needed to access the hwid service.
479 self.key_file = global_config.global_config.get_config_value(
480 'CROS', 'HWID_KEY', type=str)
481
482
483 def generate_labels(self, host):
484 hwid_labels = []
485 hwid = host.run_output('crossystem hwid').strip()
486 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
487 self.key_file).get('labels', [])
488
489 for hwid_info in hwid_info_list:
490 # If it's a prefix, we'll have:
491 # {'name': prefix_label, 'value': postfix_label} and create
492 # 'prefix_label:postfix_label'; otherwise it'll just be
493 # {'name': label} which should just be 'label'.
494 value = hwid_info.get('value', '')
495 name = hwid_info.get('name', '')
496 # There should always be a name but just in case there is not.
497 if name:
498 hwid_labels.append(name if not value else
499 '%s:%s' % (name, value))
500 return hwid_labels
501
502
503 def get_all_labels(self):
504 """We need to try all labels as a prefix and as standalone.
505
506 We don't know for sure which labels are prefix labels and which are
507 standalone so we try all of them as both.
508 """
509 all_hwid_labels = []
510 try:
511 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
512 self.key_file)
513 except IOError:
514 logging.error('Can not open key file: %s', self.key_file)
515 except hwid_lib.HwIdException as e:
516 logging.error('hwid service: %s', e)
517 return all_hwid_labels, all_hwid_labels
518
519
Kevin Chenga2619dc2016-03-28 11:42:08 -0700520CROS_LABELS = [
521 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700522 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700523 AudioLoopbackDongleLabel(),
524 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000525 BoardLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700526 ChameleonConnectionLabel(),
527 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800528 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700529 common_label.OSLabel(),
Rohit Makasana5a153502016-06-13 15:50:09 -0700530 CTSArchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700531 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700532 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700533 InternalDisplayLabel(),
534 LightSensorLabel(),
535 LucidSleepLabel(),
536 PowerSupplyLabel(),
537 ServoLabel(),
538 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700539 VideoGlitchLabel(),
540 VideoLabel(),
541]