blob: 98f5476e656794d3bb4cc60581e1614e1ef34336 [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
Kevin Chenga2619dc2016-03-28 11:42:08 -0700345class VideoGlitchLabel(base_label.BaseLabel):
346 """Label indicates if host supports video glitch detection tests."""
347
348 _NAME = 'video_glitch_detection'
349
350 def exists(self, host):
351 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
352
353 return board in video_test_constants.SUPPORTED_BOARDS
354
355
Kevin Chenga2619dc2016-03-28 11:42:08 -0700356class InternalDisplayLabel(base_label.StringLabel):
357 """Label that determines if the device has an internal display."""
358
359 _NAME = 'internal_display'
360
361 def generate_labels(self, host):
362 from autotest_lib.client.cros.graphics import graphics_utils
363 from autotest_lib.client.common_lib import utils as common_utils
364
365 def __system_output(cmd):
366 return host.run(cmd).stdout
367
368 def __read_file(remote_path):
369 return host.run('cat %s' % remote_path).stdout
370
371 # Hijack the necessary client functions so that we can take advantage
372 # of the client lib here.
373 # FIXME: find a less hacky way than this
374 original_system_output = utils.system_output
375 original_read_file = common_utils.read_file
376 utils.system_output = __system_output
377 common_utils.read_file = __read_file
378 try:
379 return ([self._NAME]
380 if graphics_utils.has_internal_display()
381 else [])
382 finally:
383 utils.system_output = original_system_output
384 common_utils.read_file = original_read_file
385
386
387class LucidSleepLabel(base_label.BaseLabel):
388 """Label that determines if device has support for lucid sleep."""
389
390 # TODO(kevcheng): See if we can determine if this label is applicable a
391 # better way (crbug.com/592146).
392 _NAME = 'lucidsleep'
393 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
394
395 def exists(self, host):
396 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
397 return board in self.LUCID_SLEEP_BOARDS
398
399
Kevin Cheng80ad5732016-03-31 16:01:56 -0700400class HWIDLabel(base_label.StringLabel):
401 """Return all the labels generated from the hwid."""
402
403 # We leave out _NAME because hwid_lib will generate everything for us.
404
405 def __init__(self):
406 # Grab the key file needed to access the hwid service.
407 self.key_file = global_config.global_config.get_config_value(
408 'CROS', 'HWID_KEY', type=str)
409
410
411 def generate_labels(self, host):
412 hwid_labels = []
413 hwid = host.run_output('crossystem hwid').strip()
414 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
415 self.key_file).get('labels', [])
416
417 for hwid_info in hwid_info_list:
418 # If it's a prefix, we'll have:
419 # {'name': prefix_label, 'value': postfix_label} and create
420 # 'prefix_label:postfix_label'; otherwise it'll just be
421 # {'name': label} which should just be 'label'.
422 value = hwid_info.get('value', '')
423 name = hwid_info.get('name', '')
424 # There should always be a name but just in case there is not.
425 if name:
426 hwid_labels.append(name if not value else
427 '%s:%s' % (name, value))
428 return hwid_labels
429
430
431 def get_all_labels(self):
432 """We need to try all labels as a prefix and as standalone.
433
434 We don't know for sure which labels are prefix labels and which are
435 standalone so we try all of them as both.
436 """
437 all_hwid_labels = []
438 try:
439 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
440 self.key_file)
441 except IOError:
442 logging.error('Can not open key file: %s', self.key_file)
443 except hwid_lib.HwIdException as e:
444 logging.error('hwid service: %s', e)
445 return all_hwid_labels, all_hwid_labels
446
447
Kevin Chenga2619dc2016-03-28 11:42:08 -0700448CROS_LABELS = [
449 AccelsLabel(),
450 AudioLoopbackDongleLabel(),
451 BluetoothLabel(),
452 BoardLabel(),
453 ChameleonConnectionLabel(),
454 ChameleonLabel(),
455 common_label.OSLabel(),
Rohit Makasana5a153502016-06-13 15:50:09 -0700456 CTSArchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700457 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700458 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700459 InternalDisplayLabel(),
460 LightSensorLabel(),
461 LucidSleepLabel(),
462 PowerSupplyLabel(),
463 ServoLabel(),
464 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700465 VideoGlitchLabel(),
466 VideoLabel(),
467]