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