blob: e82097a6b526ecf172372d918931bc7cbd0cb8b6 [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 Chenga8455302016-08-31 20:54:41 +000025class 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 # We only want to apply the board labels once, which is when they get
32 # added to the AFE. That way we don't have to worry about the board
33 # label switching on us if the wrong builds get put on the devices.
34 # crbug.com/624207 records one event of the board label switching
35 # unexpectedly on us.
36 for label in host._afe_host.labels:
37 if label.startswith(self._NAME + ':'):
38 return [label.split(':')[-1]]
39
40 # TODO(kevcheng): for now this will dup the code in CrosHost and a
41 # separate cl will refactor the get_board in CrosHost to just return the
42 # board without the BOARD_PREFIX and all the other callers will be
43 # updated to not need to clear it out and this code will be replaced to
44 # just call the host's get_board() method.
45 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
46 run_method=host.run)
47 return [release_info['CHROMEOS_RELEASE_BOARD']]
48
49
C Shapirob05c00b2017-07-18 15:06:49 -060050class ModelLabel(base_label.StringPrefixLabel):
51 """Determine the correct model label for the device."""
52
53 _NAME = ds_constants.MODEL_LABEL
54
55 def generate_labels(self, host):
56 # Return the existing label if set to defend against any bad image
57 # pushes to the host. See comment in BoardLabel for more details.
58 for label in host._afe_host.labels:
59 if label.startswith(self._NAME + ':'):
60 return [label.split(':')[-1]]
61
62 cmd = "mosys platform model"
63 result = host.run(command=cmd, ignore_status=True)
64 if result.exit_status == 0:
C Shapiro1b62b332017-07-31 13:34:43 -060065 return result.stdout
C Shapirob05c00b2017-07-18 15:06:49 -060066 else:
67 logging.info("%s exited with status %d", cmd, result.exit_status)
68 return ""
69
70
Kevin Chenga2619dc2016-03-28 11:42:08 -070071class LightSensorLabel(base_label.BaseLabel):
72 """Label indicating if a light sensor is detected."""
73
74 _NAME = 'lightsensor'
75 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
76 _LIGHTSENSOR_FILES = [
77 "in_illuminance0_input",
78 "in_illuminance_input",
79 "in_illuminance0_raw",
80 "in_illuminance_raw",
81 "illuminance0_input",
82 ]
83
84 def exists(self, host):
85 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
86 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
87 # Run the search cmd following the symlinks. Stderr_tee is set to
88 # None as there can be a symlink loop, but the command will still
89 # execute correctly with a few messages printed to stderr.
90 result = host.run(search_cmd, stdout_tee=None, stderr_tee=None,
91 ignore_status=True)
92
93 return result.exit_status == 0
94
95
96class BluetoothLabel(base_label.BaseLabel):
97 """Label indicating if bluetooth is detected."""
98
99 _NAME = 'bluetooth'
100
101 def exists(self, host):
102 result = host.run('test -d /sys/class/bluetooth/hci0',
103 ignore_status=True)
104
105 return result.exit_status == 0
106
107
108class ECLabel(base_label.BaseLabel):
109 """Label to determine the type of EC on this host."""
110
111 _NAME = 'ec:cros'
112
113 def exists(self, host):
114 cmd = 'mosys ec info'
115 # The output should look like these, so that the last field should
116 # match our EC version scheme:
117 #
118 # stm | stm32f100 | snow_v1.3.139-375eb9f
119 # ti | Unknown-10de | peppy_v1.5.114-5d52788
120 #
121 # Non-Chrome OS ECs will look like these:
122 #
123 # ENE | KB932 | 00BE107A00
124 # ite | it8518 | 3.08
125 #
126 # And some systems don't have ECs at all (Lumpy, for example).
127 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
128
129 ecinfo = host.run(command=cmd, ignore_status=True)
130 if ecinfo.exit_status == 0:
131 res = re.search(regexp, ecinfo.stdout)
132 if res:
133 logging.info("EC version is %s", res.groups()[0])
134 return True
135 logging.info("%s got: %s", cmd, ecinfo.stdout)
136 # Has an EC, but it's not a Chrome OS EC
137 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
138 return False
139
140
141class AccelsLabel(base_label.BaseLabel):
142 """Determine the type of accelerometers on this host."""
143
144 _NAME = 'accel:cros-ec'
145
146 def exists(self, host):
147 # Check to make sure we have ectool
148 rv = host.run('which ectool', ignore_status=True)
149 if rv.exit_status:
150 logging.info("No ectool cmd found; assuming no EC accelerometers")
151 return False
152
153 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700154 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700155 if rv.exit_status:
156 logging.info("EC does not support motionsense command; "
157 "assuming no EC accelerometers")
158 return False
159
160 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700161 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700162 if active[0] == "0":
163 logging.info("Motion sense inactive; assuming no EC accelerometers")
164 return False
165
166 logging.info("EC accelerometers found")
167 return True
168
169
170class ChameleonLabel(base_label.BaseLabel):
171 """Determine if a Chameleon is connected to this host."""
172
173 _NAME = 'chameleon'
174
175 def exists(self, host):
176 return host._chameleon_host is not None
177
178
179class ChameleonConnectionLabel(base_label.StringPrefixLabel):
180 """Return the Chameleon connection label."""
181
182 _NAME = 'chameleon'
183
184 def exists(self, host):
185 return host._chameleon_host is not None
186
Joseph Hwangeac44312016-08-31 12:08:38 +0800187
Kevin Chenga2619dc2016-03-28 11:42:08 -0700188 def generate_labels(self, host):
189 return [host.chameleon.get_label()]
190
191
Joseph Hwangeac44312016-08-31 12:08:38 +0800192class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
193 """Return the Chameleon peripherals labels.
194
195 The 'chameleon:bt_hid' label is applied if the bluetooth
196 classic hid device, i.e, RN-42 emulation kit, is detected.
197
198 Any peripherals plugged into the chameleon board would be
199 detected and applied proper labels in this class.
200 """
201
202 _NAME = 'chameleon'
203
204 def exists(self, host):
205 return host._chameleon_host is not None
206
207
208 def generate_labels(self, host):
209 bt_hid_device = host.chameleon.get_bluetooh_hid_mouse()
210 return ['bt_hid'] if bt_hid_device.CheckSerialConnection() else []
211
212
Kevin Chenga2619dc2016-03-28 11:42:08 -0700213class AudioLoopbackDongleLabel(base_label.BaseLabel):
214 """Return the label if an audio loopback dongle is plugged in."""
215
216 _NAME = 'audio_loopback_dongle'
217
218 def exists(self, host):
219 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
220 ignore_status=True).stdout
221 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
222 cras_utils.node_type_is_plugged('MIC', nodes_info)):
223 return True
224 return False
225
226
227class PowerSupplyLabel(base_label.StringPrefixLabel):
228 """
229 Return the label describing the power supply type.
230
231 Labels representing this host's power supply.
232 * `power:battery` when the device has a battery intended for
233 extended use
234 * `power:AC_primary` when the device has a battery not intended
235 for extended use (for moving the machine, etc)
236 * `power:AC_only` when the device has no battery at all.
237 """
238
239 _NAME = 'power'
240
241 def __init__(self):
242 self.psu_cmd_result = None
243
244
245 def exists(self, host):
246 self.psu_cmd_result = host.run(command='mosys psu type',
247 ignore_status=True)
248 return self.psu_cmd_result.stdout.strip() != 'unknown'
249
250
251 def generate_labels(self, host):
252 if self.psu_cmd_result.exit_status:
253 # The psu command for mosys is not included for all platforms. The
254 # assumption is that the device will have a battery if the command
255 # is not found.
256 return ['battery']
257 return [self.psu_cmd_result.stdout.strip()]
258
259
260class StorageLabel(base_label.StringPrefixLabel):
261 """
262 Return the label describing the storage type.
263
264 Determine if the internal device is SCSI or dw_mmc device.
265 Then check that it is SSD or HDD or eMMC or something else.
266
267 Labels representing this host's internal device type:
268 * `storage:ssd` when internal device is solid state drive
269 * `storage:hdd` when internal device is hard disk drive
270 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700271 * `storage:nvme` when internal device is NVMe drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700272 * None When internal device is something else or
273 when we are unable to determine the type
274 """
275
276 _NAME = 'storage'
277
278 def __init__(self):
279 self.type_str = ''
280
281
282 def exists(self, host):
283 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
284 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
285 '. /usr/share/misc/chromeos-common.sh;',
286 'load_base_vars;',
287 'get_fixed_dst_drive'])
288 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
289 if rootdev.exit_status:
290 logging.info("Fail to run %s", rootdev_cmd)
291 return False
292 rootdev_str = rootdev.stdout.strip()
293
294 if not rootdev_str:
295 return False
296
297 rootdev_base = os.path.basename(rootdev_str)
298
299 mmc_pattern = '/dev/mmcblk[0-9]'
300 if re.match(mmc_pattern, rootdev_str):
301 # Use type to determine if the internal device is eMMC or somthing
302 # else. We can assume that MMC is always an internal device.
303 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
304 type = host.run(command=type_cmd, ignore_status=True)
305 if type.exit_status:
306 logging.info("Fail to run %s", type_cmd)
307 return False
308 type_str = type.stdout.strip()
309
310 if type_str == 'MMC':
311 self.type_str = 'mmc'
312 return True
313
314 scsi_pattern = '/dev/sd[a-z]+'
315 if re.match(scsi_pattern, rootdev.stdout):
316 # Read symlink for /sys/block/sd* to determine if the internal
317 # device is connected via ata or usb.
318 link_cmd = 'readlink /sys/block/%s' % rootdev_base
319 link = host.run(command=link_cmd, ignore_status=True)
320 if link.exit_status:
321 logging.info("Fail to run %s", link_cmd)
322 return False
323 link_str = link.stdout.strip()
324 if 'usb' in link_str:
325 return False
326
327 # Read rotation to determine if the internal device is ssd or hdd.
328 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
329 % rootdev_base)
330 rotate = host.run(command=rotate_cmd, ignore_status=True)
331 if rotate.exit_status:
332 logging.info("Fail to run %s", rotate_cmd)
333 return False
334 rotate_str = rotate.stdout.strip()
335
336 rotate_dict = {'0':'ssd', '1':'hdd'}
337 self.type_str = rotate_dict.get(rotate_str)
338 return True
339
Gwendal Grignou327fec62017-07-26 15:25:43 -0700340 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
341 if re.match(nvme_pattern, rootdev_str):
342 self.type_str = 'nmve'
343 return True
344
Kevin Chenga2619dc2016-03-28 11:42:08 -0700345 # All other internal device / error case will always fall here
346 return False
347
348
349 def generate_labels(self, host):
350 return [self.type_str]
351
352
353class ServoLabel(base_label.BaseLabel):
354 """Label to apply if a servo is present."""
355
356 _NAME = 'servo'
357
358 def exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700359 """
360 Check if the servo label should apply to the host or not.
361
362 @returns True if a servo host is detected, False otherwise.
363 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700364 servo_host_hostname = None
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700365 servo_args, _ = servo_host._get_standard_servo_args(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700366 if servo_args:
367 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700368 return (servo_host_hostname is not None
369 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700370
371
372class VideoLabel(base_label.StringLabel):
373 """Labels detailing video capabilities."""
374
375 # List gathered from
376 # https://chromium.googlesource.com/chromiumos/
377 # platform2/+/master/avtest_label_detect/main.c#19
378 _NAME = [
379 'hw_jpeg_acc_dec',
380 'hw_video_acc_h264',
381 'hw_video_acc_vp8',
382 'hw_video_acc_vp9',
383 'hw_video_acc_enc_h264',
384 'hw_video_acc_enc_vp8',
385 'webcam',
386 ]
387
388 def generate_labels(self, host):
389 result = host.run('/usr/local/bin/avtest_label_detect',
390 ignore_status=True).stdout
391 return re.findall('^Detected label: (\w+)$', result, re.M)
392
393
Rohit Makasana5a153502016-06-13 15:50:09 -0700394class CTSArchLabel(base_label.StringLabel):
395 """Labels to determine CTS abi."""
396
397 _NAME = ['cts_abi_arm', 'cts_abi_x86']
398
399 def _get_cts_abis(self, host):
400 """Return supported CTS ABIs.
401
402 @return List of supported CTS bundle ABIs.
403 """
404 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
405 return cts_abis.get(host.get_cpu_arch(), [])
406
407
408 def generate_labels(self, host):
409 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
410
411
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700412class ArcLabel(base_label.BaseLabel):
413 """Label indicates if host has ARC support."""
414
415 _NAME = 'arc'
416
Kevin Chengeee38e02016-08-30 16:13:23 -0700417 @base_label.forever_exists_decorate
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700418 def exists(self, host):
419 return 0 == host.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
420 ignore_status=True).exit_status
421
422
Kevin Chenga2619dc2016-03-28 11:42:08 -0700423class VideoGlitchLabel(base_label.BaseLabel):
424 """Label indicates if host supports video glitch detection tests."""
425
426 _NAME = 'video_glitch_detection'
427
428 def exists(self, host):
429 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
430
431 return board in video_test_constants.SUPPORTED_BOARDS
432
433
Kevin Chenga2619dc2016-03-28 11:42:08 -0700434class InternalDisplayLabel(base_label.StringLabel):
435 """Label that determines if the device has an internal display."""
436
437 _NAME = 'internal_display'
438
439 def generate_labels(self, host):
440 from autotest_lib.client.cros.graphics import graphics_utils
441 from autotest_lib.client.common_lib import utils as common_utils
442
443 def __system_output(cmd):
444 return host.run(cmd).stdout
445
446 def __read_file(remote_path):
447 return host.run('cat %s' % remote_path).stdout
448
449 # Hijack the necessary client functions so that we can take advantage
450 # of the client lib here.
451 # FIXME: find a less hacky way than this
452 original_system_output = utils.system_output
453 original_read_file = common_utils.read_file
454 utils.system_output = __system_output
455 common_utils.read_file = __read_file
456 try:
457 return ([self._NAME]
458 if graphics_utils.has_internal_display()
459 else [])
460 finally:
461 utils.system_output = original_system_output
462 common_utils.read_file = original_read_file
463
464
465class LucidSleepLabel(base_label.BaseLabel):
466 """Label that determines if device has support for lucid sleep."""
467
468 # TODO(kevcheng): See if we can determine if this label is applicable a
469 # better way (crbug.com/592146).
470 _NAME = 'lucidsleep'
471 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
472
473 def exists(self, host):
474 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
475 return board in self.LUCID_SLEEP_BOARDS
476
477
Kevin Cheng80ad5732016-03-31 16:01:56 -0700478class HWIDLabel(base_label.StringLabel):
479 """Return all the labels generated from the hwid."""
480
481 # We leave out _NAME because hwid_lib will generate everything for us.
482
483 def __init__(self):
484 # Grab the key file needed to access the hwid service.
485 self.key_file = global_config.global_config.get_config_value(
486 'CROS', 'HWID_KEY', type=str)
487
488
489 def generate_labels(self, host):
490 hwid_labels = []
491 hwid = host.run_output('crossystem hwid').strip()
492 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
493 self.key_file).get('labels', [])
494
495 for hwid_info in hwid_info_list:
496 # If it's a prefix, we'll have:
497 # {'name': prefix_label, 'value': postfix_label} and create
498 # 'prefix_label:postfix_label'; otherwise it'll just be
499 # {'name': label} which should just be 'label'.
500 value = hwid_info.get('value', '')
501 name = hwid_info.get('name', '')
502 # There should always be a name but just in case there is not.
503 if name:
504 hwid_labels.append(name if not value else
505 '%s:%s' % (name, value))
506 return hwid_labels
507
508
509 def get_all_labels(self):
510 """We need to try all labels as a prefix and as standalone.
511
512 We don't know for sure which labels are prefix labels and which are
513 standalone so we try all of them as both.
514 """
515 all_hwid_labels = []
516 try:
517 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
518 self.key_file)
519 except IOError:
520 logging.error('Can not open key file: %s', self.key_file)
521 except hwid_lib.HwIdException as e:
522 logging.error('hwid service: %s', e)
523 return all_hwid_labels, all_hwid_labels
524
525
Kevin Chenga2619dc2016-03-28 11:42:08 -0700526CROS_LABELS = [
527 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700528 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700529 AudioLoopbackDongleLabel(),
530 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000531 BoardLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700532 ChameleonConnectionLabel(),
533 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800534 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700535 common_label.OSLabel(),
Rohit Makasana5a153502016-06-13 15:50:09 -0700536 CTSArchLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700537 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700538 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700539 InternalDisplayLabel(),
540 LightSensorLabel(),
541 LucidSleepLabel(),
542 PowerSupplyLabel(),
543 ServoLabel(),
544 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700545 VideoGlitchLabel(),
546 VideoLabel(),
547]