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