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