blob: 5e34aff2012d7fb51679c64cafeb85aa71d00d07 [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
166 def generate_labels(self, host):
167 return [host.chameleon.get_label()]
168
169
170class AudioLoopbackDongleLabel(base_label.BaseLabel):
171 """Return the label if an audio loopback dongle is plugged in."""
172
173 _NAME = 'audio_loopback_dongle'
174
175 def exists(self, host):
176 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
177 ignore_status=True).stdout
178 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
179 cras_utils.node_type_is_plugged('MIC', nodes_info)):
180 return True
181 return False
182
183
184class PowerSupplyLabel(base_label.StringPrefixLabel):
185 """
186 Return the label describing the power supply type.
187
188 Labels representing this host's power supply.
189 * `power:battery` when the device has a battery intended for
190 extended use
191 * `power:AC_primary` when the device has a battery not intended
192 for extended use (for moving the machine, etc)
193 * `power:AC_only` when the device has no battery at all.
194 """
195
196 _NAME = 'power'
197
198 def __init__(self):
199 self.psu_cmd_result = None
200
201
202 def exists(self, host):
203 self.psu_cmd_result = host.run(command='mosys psu type',
204 ignore_status=True)
205 return self.psu_cmd_result.stdout.strip() != 'unknown'
206
207
208 def generate_labels(self, host):
209 if self.psu_cmd_result.exit_status:
210 # The psu command for mosys is not included for all platforms. The
211 # assumption is that the device will have a battery if the command
212 # is not found.
213 return ['battery']
214 return [self.psu_cmd_result.stdout.strip()]
215
216
217class StorageLabel(base_label.StringPrefixLabel):
218 """
219 Return the label describing the storage type.
220
221 Determine if the internal device is SCSI or dw_mmc device.
222 Then check that it is SSD or HDD or eMMC or something else.
223
224 Labels representing this host's internal device type:
225 * `storage:ssd` when internal device is solid state drive
226 * `storage:hdd` when internal device is hard disk drive
227 * `storage:mmc` when internal device is mmc drive
228 * None When internal device is something else or
229 when we are unable to determine the type
230 """
231
232 _NAME = 'storage'
233
234 def __init__(self):
235 self.type_str = ''
236
237
238 def exists(self, host):
239 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
240 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
241 '. /usr/share/misc/chromeos-common.sh;',
242 'load_base_vars;',
243 'get_fixed_dst_drive'])
244 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
245 if rootdev.exit_status:
246 logging.info("Fail to run %s", rootdev_cmd)
247 return False
248 rootdev_str = rootdev.stdout.strip()
249
250 if not rootdev_str:
251 return False
252
253 rootdev_base = os.path.basename(rootdev_str)
254
255 mmc_pattern = '/dev/mmcblk[0-9]'
256 if re.match(mmc_pattern, rootdev_str):
257 # Use type to determine if the internal device is eMMC or somthing
258 # else. We can assume that MMC is always an internal device.
259 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
260 type = host.run(command=type_cmd, ignore_status=True)
261 if type.exit_status:
262 logging.info("Fail to run %s", type_cmd)
263 return False
264 type_str = type.stdout.strip()
265
266 if type_str == 'MMC':
267 self.type_str = 'mmc'
268 return True
269
270 scsi_pattern = '/dev/sd[a-z]+'
271 if re.match(scsi_pattern, rootdev.stdout):
272 # Read symlink for /sys/block/sd* to determine if the internal
273 # device is connected via ata or usb.
274 link_cmd = 'readlink /sys/block/%s' % rootdev_base
275 link = host.run(command=link_cmd, ignore_status=True)
276 if link.exit_status:
277 logging.info("Fail to run %s", link_cmd)
278 return False
279 link_str = link.stdout.strip()
280 if 'usb' in link_str:
281 return False
282
283 # Read rotation to determine if the internal device is ssd or hdd.
284 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
285 % rootdev_base)
286 rotate = host.run(command=rotate_cmd, ignore_status=True)
287 if rotate.exit_status:
288 logging.info("Fail to run %s", rotate_cmd)
289 return False
290 rotate_str = rotate.stdout.strip()
291
292 rotate_dict = {'0':'ssd', '1':'hdd'}
293 self.type_str = rotate_dict.get(rotate_str)
294 return True
295
296 # All other internal device / error case will always fall here
297 return False
298
299
300 def generate_labels(self, host):
301 return [self.type_str]
302
303
304class ServoLabel(base_label.BaseLabel):
305 """Label to apply if a servo is present."""
306
307 _NAME = 'servo'
308
309 def exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700310 """
311 Check if the servo label should apply to the host or not.
312
313 @returns True if a servo host is detected, False otherwise.
314 """
315 servo_args, _ = servo_host._get_standard_servo_args(host)
316 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
317 return (servo_host_hostname is not None
318 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700319
320
321class VideoLabel(base_label.StringLabel):
322 """Labels detailing video capabilities."""
323
324 # List gathered from
325 # https://chromium.googlesource.com/chromiumos/
326 # platform2/+/master/avtest_label_detect/main.c#19
327 _NAME = [
328 'hw_jpeg_acc_dec',
329 'hw_video_acc_h264',
330 'hw_video_acc_vp8',
331 'hw_video_acc_vp9',
332 'hw_video_acc_enc_h264',
333 'hw_video_acc_enc_vp8',
334 'webcam',
335 ]
336
337 def generate_labels(self, host):
338 result = host.run('/usr/local/bin/avtest_label_detect',
339 ignore_status=True).stdout
340 return re.findall('^Detected label: (\w+)$', result, re.M)
341
342
Rohit Makasana5a153502016-06-13 15:50:09 -0700343class CTSArchLabel(base_label.StringLabel):
344 """Labels to determine CTS abi."""
345
346 _NAME = ['cts_abi_arm', 'cts_abi_x86']
347
348 def _get_cts_abis(self, host):
349 """Return supported CTS ABIs.
350
351 @return List of supported CTS bundle ABIs.
352 """
353 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
354 return cts_abis.get(host.get_cpu_arch(), [])
355
356
357 def generate_labels(self, host):
358 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
359
360
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700361class ArcLabel(base_label.BaseLabel):
362 """Label indicates if host has ARC support."""
363
364 _NAME = 'arc'
365
366 def exists(self, host):
367 return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
368 ignore_status=True).exit_status
369
370
Kevin Chenga2619dc2016-03-28 11:42:08 -0700371class VideoGlitchLabel(base_label.BaseLabel):
372 """Label indicates if host supports video glitch detection tests."""
373
374 _NAME = 'video_glitch_detection'
375
376 def exists(self, host):
377 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
378
379 return board in video_test_constants.SUPPORTED_BOARDS
380
381
Kevin Chenga2619dc2016-03-28 11:42:08 -0700382class InternalDisplayLabel(base_label.StringLabel):
383 """Label that determines if the device has an internal display."""
384
385 _NAME = 'internal_display'
386
387 def generate_labels(self, host):
388 from autotest_lib.client.cros.graphics import graphics_utils
389 from autotest_lib.client.common_lib import utils as common_utils
390
391 def __system_output(cmd):
392 return host.run(cmd).stdout
393
394 def __read_file(remote_path):
395 return host.run('cat %s' % remote_path).stdout
396
397 # Hijack the necessary client functions so that we can take advantage
398 # of the client lib here.
399 # FIXME: find a less hacky way than this
400 original_system_output = utils.system_output
401 original_read_file = common_utils.read_file
402 utils.system_output = __system_output
403 common_utils.read_file = __read_file
404 try:
405 return ([self._NAME]
406 if graphics_utils.has_internal_display()
407 else [])
408 finally:
409 utils.system_output = original_system_output
410 common_utils.read_file = original_read_file
411
412
413class LucidSleepLabel(base_label.BaseLabel):
414 """Label that determines if device has support for lucid sleep."""
415
416 # TODO(kevcheng): See if we can determine if this label is applicable a
417 # better way (crbug.com/592146).
418 _NAME = 'lucidsleep'
419 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
420
421 def exists(self, host):
422 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
423 return board in self.LUCID_SLEEP_BOARDS
424
425
Kevin Cheng80ad5732016-03-31 16:01:56 -0700426class HWIDLabel(base_label.StringLabel):
427 """Return all the labels generated from the hwid."""
428
429 # We leave out _NAME because hwid_lib will generate everything for us.
430
431 def __init__(self):
432 # Grab the key file needed to access the hwid service.
433 self.key_file = global_config.global_config.get_config_value(
434 'CROS', 'HWID_KEY', type=str)
435
436
437 def generate_labels(self, host):
438 hwid_labels = []
439 hwid = host.run_output('crossystem hwid').strip()
440 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
441 self.key_file).get('labels', [])
442
443 for hwid_info in hwid_info_list:
444 # If it's a prefix, we'll have:
445 # {'name': prefix_label, 'value': postfix_label} and create
446 # 'prefix_label:postfix_label'; otherwise it'll just be
447 # {'name': label} which should just be 'label'.
448 value = hwid_info.get('value', '')
449 name = hwid_info.get('name', '')
450 # There should always be a name but just in case there is not.
451 if name:
452 hwid_labels.append(name if not value else
453 '%s:%s' % (name, value))
454 return hwid_labels
455
456
457 def get_all_labels(self):
458 """We need to try all labels as a prefix and as standalone.
459
460 We don't know for sure which labels are prefix labels and which are
461 standalone so we try all of them as both.
462 """
463 all_hwid_labels = []
464 try:
465 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
466 self.key_file)
467 except IOError:
468 logging.error('Can not open key file: %s', self.key_file)
469 except hwid_lib.HwIdException as e:
470 logging.error('hwid service: %s', e)
471 return all_hwid_labels, all_hwid_labels
472
473
Kevin Chenga2619dc2016-03-28 11:42:08 -0700474CROS_LABELS = [
475 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700476 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700477 AudioLoopbackDongleLabel(),
478 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000479 BoardLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700480 ChameleonConnectionLabel(),
481 ChameleonLabel(),
482 common_label.OSLabel(),
Rohit Makasana5a153502016-06-13 15:50:09 -0700483 CTSArchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700484 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700485 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700486 InternalDisplayLabel(),
487 LightSensorLabel(),
488 LucidSleepLabel(),
489 PowerSupplyLabel(),
490 ServoLabel(),
491 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700492 VideoGlitchLabel(),
493 VideoLabel(),
494]