blob: 22ec33b6c2310355bfb55514ac8274cc1ef8b033 [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 """
337 servo_args, _ = servo_host._get_standard_servo_args(host)
338 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
339 return (servo_host_hostname is not None
340 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700341
342
343class VideoLabel(base_label.StringLabel):
344 """Labels detailing video capabilities."""
345
346 # List gathered from
347 # https://chromium.googlesource.com/chromiumos/
348 # platform2/+/master/avtest_label_detect/main.c#19
349 _NAME = [
350 'hw_jpeg_acc_dec',
351 'hw_video_acc_h264',
352 'hw_video_acc_vp8',
353 'hw_video_acc_vp9',
354 'hw_video_acc_enc_h264',
355 'hw_video_acc_enc_vp8',
356 'webcam',
357 ]
358
359 def generate_labels(self, host):
360 result = host.run('/usr/local/bin/avtest_label_detect',
361 ignore_status=True).stdout
362 return re.findall('^Detected label: (\w+)$', result, re.M)
363
364
Rohit Makasana5a153502016-06-13 15:50:09 -0700365class CTSArchLabel(base_label.StringLabel):
366 """Labels to determine CTS abi."""
367
368 _NAME = ['cts_abi_arm', 'cts_abi_x86']
369
370 def _get_cts_abis(self, host):
371 """Return supported CTS ABIs.
372
373 @return List of supported CTS bundle ABIs.
374 """
375 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
376 return cts_abis.get(host.get_cpu_arch(), [])
377
378
379 def generate_labels(self, host):
380 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
381
382
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700383class ArcLabel(base_label.BaseLabel):
384 """Label indicates if host has ARC support."""
385
386 _NAME = 'arc'
387
Kevin Chengeee38e02016-08-30 16:13:23 -0700388 @base_label.forever_exists_decorate
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700389 def exists(self, host):
390 return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
391 ignore_status=True).exit_status
392
393
Kevin Chenga2619dc2016-03-28 11:42:08 -0700394class VideoGlitchLabel(base_label.BaseLabel):
395 """Label indicates if host supports video glitch detection tests."""
396
397 _NAME = 'video_glitch_detection'
398
399 def exists(self, host):
400 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
401
402 return board in video_test_constants.SUPPORTED_BOARDS
403
404
Kevin Chenga2619dc2016-03-28 11:42:08 -0700405class InternalDisplayLabel(base_label.StringLabel):
406 """Label that determines if the device has an internal display."""
407
408 _NAME = 'internal_display'
409
410 def generate_labels(self, host):
411 from autotest_lib.client.cros.graphics import graphics_utils
412 from autotest_lib.client.common_lib import utils as common_utils
413
414 def __system_output(cmd):
415 return host.run(cmd).stdout
416
417 def __read_file(remote_path):
418 return host.run('cat %s' % remote_path).stdout
419
420 # Hijack the necessary client functions so that we can take advantage
421 # of the client lib here.
422 # FIXME: find a less hacky way than this
423 original_system_output = utils.system_output
424 original_read_file = common_utils.read_file
425 utils.system_output = __system_output
426 common_utils.read_file = __read_file
427 try:
428 return ([self._NAME]
429 if graphics_utils.has_internal_display()
430 else [])
431 finally:
432 utils.system_output = original_system_output
433 common_utils.read_file = original_read_file
434
435
436class LucidSleepLabel(base_label.BaseLabel):
437 """Label that determines if device has support for lucid sleep."""
438
439 # TODO(kevcheng): See if we can determine if this label is applicable a
440 # better way (crbug.com/592146).
441 _NAME = 'lucidsleep'
442 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
443
444 def exists(self, host):
445 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
446 return board in self.LUCID_SLEEP_BOARDS
447
448
Kevin Cheng80ad5732016-03-31 16:01:56 -0700449class HWIDLabel(base_label.StringLabel):
450 """Return all the labels generated from the hwid."""
451
452 # We leave out _NAME because hwid_lib will generate everything for us.
453
454 def __init__(self):
455 # Grab the key file needed to access the hwid service.
456 self.key_file = global_config.global_config.get_config_value(
457 'CROS', 'HWID_KEY', type=str)
458
459
460 def generate_labels(self, host):
461 hwid_labels = []
462 hwid = host.run_output('crossystem hwid').strip()
463 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
464 self.key_file).get('labels', [])
465
466 for hwid_info in hwid_info_list:
467 # If it's a prefix, we'll have:
468 # {'name': prefix_label, 'value': postfix_label} and create
469 # 'prefix_label:postfix_label'; otherwise it'll just be
470 # {'name': label} which should just be 'label'.
471 value = hwid_info.get('value', '')
472 name = hwid_info.get('name', '')
473 # There should always be a name but just in case there is not.
474 if name:
475 hwid_labels.append(name if not value else
476 '%s:%s' % (name, value))
477 return hwid_labels
478
479
480 def get_all_labels(self):
481 """We need to try all labels as a prefix and as standalone.
482
483 We don't know for sure which labels are prefix labels and which are
484 standalone so we try all of them as both.
485 """
486 all_hwid_labels = []
487 try:
488 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
489 self.key_file)
490 except IOError:
491 logging.error('Can not open key file: %s', self.key_file)
492 except hwid_lib.HwIdException as e:
493 logging.error('hwid service: %s', e)
494 return all_hwid_labels, all_hwid_labels
495
496
Kevin Chenga2619dc2016-03-28 11:42:08 -0700497CROS_LABELS = [
498 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700499 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700500 AudioLoopbackDongleLabel(),
501 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000502 BoardLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700503 ChameleonConnectionLabel(),
504 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800505 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700506 common_label.OSLabel(),
Rohit Makasana5a153502016-06-13 15:50:09 -0700507 CTSArchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700508 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700509 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700510 InternalDisplayLabel(),
511 LightSensorLabel(),
512 LucidSleepLabel(),
513 PowerSupplyLabel(),
514 ServoLabel(),
515 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700516 VideoGlitchLabel(),
517 VideoLabel(),
518]