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