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