blob: 689bf4ad1b650ec0c0226b7a7071d8dc843b7813 [file] [log] [blame]
Derek Beckettf73baca2020-08-19 15:08:47 -07001# Lint as: python2, python3
Kevin Chenga2619dc2016-03-28 11:42:08 -07002# Copyright 2016 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This class defines the CrosHost Label class."""
7
Derek Beckettf73baca2020-08-19 15:08:47 -07008from __future__ import absolute_import
9from __future__ import division
10from __future__ import print_function
11
C Shapiro05dd3222017-09-22 10:42:33 -060012import collections
Kevin Chenga2619dc2016-03-28 11:42:08 -070013import logging
Kevin Chenga2619dc2016-03-28 11:42:08 -070014import re
15
16import common
17
18from autotest_lib.client.bin import utils
Kevin Chenga2619dc2016-03-28 11:42:08 -070019from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
20from autotest_lib.server.hosts import base_label
21from autotest_lib.server.hosts import common_label
Garry Wang11b5e872020-03-11 15:14:08 -070022from autotest_lib.server.hosts import servo_constants
Derek Beckettf73baca2020-08-19 15:08:47 -070023from six.moves import zip
Kevin Chenga2619dc2016-03-28 11:42:08 -070024
25# pylint: disable=missing-docstring
C Shapiro05dd3222017-09-22 10:42:33 -060026LsbOutput = collections.namedtuple('LsbOutput', ['unibuild', 'board'])
27
Eshwar Narayan871a2c02020-02-06 11:15:24 -080028# Repair and Deploy taskName
29REPAIR_TASK_NAME = 'repair'
30DEPLOY_TASK_NAME = 'deploy'
31
Gregory Nisbetfb68a1f2019-08-22 10:27:33 -070032
C Shapiro05dd3222017-09-22 10:42:33 -060033def _parse_lsb_output(host):
Allen Lia0c7afc2019-02-26 15:50:06 -080034 """Parses the LSB output and returns key data points for labeling.
C Shapiro05dd3222017-09-22 10:42:33 -060035
Allen Lia0c7afc2019-02-26 15:50:06 -080036 @param host: Host that the command will be executed against
37 @returns: LsbOutput with the result of parsing the /etc/lsb-release output
38 """
39 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
40 run_method=host.run)
C Shapiro05dd3222017-09-22 10:42:33 -060041
Allen Lia0c7afc2019-02-26 15:50:06 -080042 unibuild = release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1'
43 return LsbOutput(unibuild, release_info['CHROMEOS_RELEASE_BOARD'])
C Shapiro05dd3222017-09-22 10:42:33 -060044
Kevin Chenga2619dc2016-03-28 11:42:08 -070045
C Shapirobe0ff8d2019-06-14 10:41:43 -060046class DeviceSkuLabel(base_label.StringPrefixLabel):
47 """Determine the correct device_sku label for the device."""
48
49 _NAME = ds_constants.DEVICE_SKU_LABEL
50
51 def generate_labels(self, host):
52 device_sku = host.host_info_store.get().device_sku
53 if device_sku:
54 return [device_sku]
55
56 mosys_cmd = 'mosys platform sku'
57 result = host.run(command=mosys_cmd, ignore_status=True)
58 if result.exit_status == 0:
59 return [result.stdout.strip()]
60
61 return []
62
Eshwar Narayan871a2c02020-02-06 11:15:24 -080063 def update_for_task(self, task_name):
Otabek Kasimovb80faf72020-09-15 21:22:00 -070064 # This label is stored in the lab config.
65 return task_name in (DEPLOY_TASK_NAME, REPAIR_TASK_NAME, '')
Eshwar Narayan871a2c02020-02-06 11:15:24 -080066
C Shapirobe0ff8d2019-06-14 10:41:43 -060067
Ned Nguyene0a619d2019-07-01 15:50:23 -060068class BrandCodeLabel(base_label.StringPrefixLabel):
69 """Determine the correct brand_code (aka RLZ-code) for the device."""
70
71 _NAME = ds_constants.BRAND_CODE_LABEL
72
73 def generate_labels(self, host):
74 brand_code = host.host_info_store.get().brand_code
75 if brand_code:
76 return [brand_code]
77
Greg Edelston7cea0c42019-11-26 15:17:22 -070078 cros_config_cmd = 'cros_config / brand-code'
79 result = host.run(command=cros_config_cmd, ignore_status=True)
Ned Nguyene0a619d2019-07-01 15:50:23 -060080 if result.exit_status == 0:
81 return [result.stdout.strip()]
82
83 return []
84
85
Shijin Abrahamc09587d2020-02-14 20:46:55 -080086class BluetoothPeerLabel(base_label.StringPrefixLabel):
87 """Return the Bluetooth peer labels.
88
89 working_bluetooth_btpeer label is applied if a Raspberry Pi Bluetooth peer
90 is detected.There can be up to 4 Bluetooth peers. Labels
91 working_bluetooth_btpeer:[1-4] will be assigned depending on the number of
92 peers present.
93
94 """
95
96 _NAME = 'working_bluetooth_btpeer'
97
98 def exists(self, host):
99 return len(host._btpeer_host_list) > 0
100
101 def generate_labels(self, host):
102 labels_list = []
103 count = 1
104
105 for (btpeer, btpeer_host) in \
106 zip(host.btpeer_list, host._btpeer_host_list):
107 try:
108 # Initialize one device type to make sure the peer is working
109 bt_hid_device = btpeer.get_bluetooth_hid_mouse()
110 if bt_hid_device.CheckSerialConnection():
111 labels_list.append(str(count))
112 count += 1
113 except Exception as e:
114 logging.error('Error with initializing bt_hid_mouse on '
115 'btpeer %s %s', btpeer_host.hostname, e)
116
117 logging.info('Bluetooth Peer labels are %s', labels_list)
118 return labels_list
119
120 def update_for_task(self, task_name):
Shijin Abraham76bc1db2020-03-06 10:52:10 -0800121 # This label is stored in the state config, so only repair tasks update
122 # it or when no task name is mentioned.
123 return task_name in (REPAIR_TASK_NAME, '')
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800124
Kevin Chenga2619dc2016-03-28 11:42:08 -0700125
Mary Ruthven935ebad2018-06-13 16:13:20 -0700126class Cr50Label(base_label.StringPrefixLabel):
Mary Ruthven6c462642019-09-17 19:13:36 -0700127 """Label indicating the cr50 image type."""
Mary Ruthven935ebad2018-06-13 16:13:20 -0700128
129 _NAME = 'cr50'
130
131 def __init__(self):
132 self.ver = None
133
Mary Ruthven935ebad2018-06-13 16:13:20 -0700134 def exists(self, host):
135 # Make sure the gsctool version command runs ok
136 self.ver = host.run('gsctool -a -f', ignore_status=True)
137 return self.ver.exit_status == 0
138
Mary Ruthven6c462642019-09-17 19:13:36 -0700139 def _get_version(self, region):
140 """Get the version number of the given region"""
141 return re.search(region + ' (\d+\.\d+\.\d+)', self.ver.stdout).group(1)
Mary Ruthven935ebad2018-06-13 16:13:20 -0700142
143 def generate_labels(self, host):
144 # Check the major version to determine prePVT vs PVT
Mary Ruthven6c462642019-09-17 19:13:36 -0700145 version = self._get_version('RW')
146 major_version = int(version.split('.')[1])
Mary Ruthven935ebad2018-06-13 16:13:20 -0700147 # PVT images have a odd major version prePVT have even
Mary Ruthven6c462642019-09-17 19:13:36 -0700148 return ['pvt' if (major_version % 2) else 'prepvt']
149
Xixuan Wu61b2b262020-03-06 10:09:55 -0800150 def update_for_task(self, task_name):
151 # This label is stored in the state config, so only repair tasks update
152 # it or when no task name is mentioned.
153 return task_name in (REPAIR_TASK_NAME, '')
154
Mary Ruthven6c462642019-09-17 19:13:36 -0700155
156class Cr50RWKeyidLabel(Cr50Label):
157 """Label indicating the cr50 RW version."""
158 _REGION = 'RW'
159 _NAME = 'cr50-rw-keyid'
160
161 def _get_keyid_info(self, region):
162 """Get the keyid of the given region."""
163 match = re.search('keyids:.*%s (\S+)' % region, self.ver.stdout)
164 keyid = match.group(1).rstrip(',')
165 is_prod = int(keyid, 16) & (1 << 2)
166 return [keyid, 'prod' if is_prod else 'dev']
167
168 def generate_labels(self, host):
169 """Get the key type."""
170 return self._get_keyid_info(self._REGION)
171
172
173class Cr50ROKeyidLabel(Cr50RWKeyidLabel):
174 """Label indicating the RO key type."""
175 _REGION = 'RO'
176 _NAME = 'cr50-ro-keyid'
177
178
Kevin Chenga2619dc2016-03-28 11:42:08 -0700179class ChameleonLabel(base_label.BaseLabel):
180 """Determine if a Chameleon is connected to this host."""
181
182 _NAME = 'chameleon'
183
184 def exists(self, host):
Xixuan Wu7afb54f2019-09-17 11:45:20 -0700185 # See crbug.com/1004500#2 for details.
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800186 has_chameleon = host._chameleon_host is not None
Gregory Nisbetb2f6d792019-09-11 14:30:47 -0700187 # TODO(crbug.com/995900) -- debug why chameleon label is flipping
188 try:
189 logging.info("has_chameleon %s", has_chameleon)
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800190 logging.info("_chameleon_host %s",
191 getattr(host, "_chameleon_host", "NO_ATTRIBUTE"))
192 logging.info("chameleon %s",
193 getattr(host, "chameleon", "NO_ATTRIBUTE"))
Gregory Nisbetb2f6d792019-09-11 14:30:47 -0700194 except:
195 pass
196 return has_chameleon
197
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800198 def update_for_task(self, task_name):
199 # This label is stored in the state config, so only repair tasks update
200 # it or when no task name is mentioned.
201 return task_name in (REPAIR_TASK_NAME, '')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700202
203
204class ChameleonConnectionLabel(base_label.StringPrefixLabel):
205 """Return the Chameleon connection label."""
206
207 _NAME = 'chameleon'
208
209 def exists(self, host):
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800210 return host._chameleon_host is not None
Joseph Hwangeac44312016-08-31 12:08:38 +0800211
Kevin Chenga2619dc2016-03-28 11:42:08 -0700212 def generate_labels(self, host):
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800213 return [host.chameleon.get_label()]
Kevin Chenga2619dc2016-03-28 11:42:08 -0700214
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800215 def update_for_task(self, task_name):
216 # This label is stored in the lab config, so only deploy tasks update it
217 # or when no task name is mentioned.
218 return task_name in (DEPLOY_TASK_NAME, '')
219
Kevin Chenga2619dc2016-03-28 11:42:08 -0700220
221class AudioLoopbackDongleLabel(base_label.BaseLabel):
222 """Return the label if an audio loopback dongle is plugged in."""
223
224 _NAME = 'audio_loopback_dongle'
225
226 def exists(self, host):
Gregory Nisbete280ea22019-08-16 17:50:03 -0700227 # Based on crbug.com/991285, AudioLoopbackDongle sometimes flips.
228 # Ensure that AudioLoopbackDongle.exists returns True
229 # forever, after it returns True *once*.
230 if self._cached_exists(host):
231 # If the current state is True, return it, don't run the command on
232 # the DUT and potentially flip the state.
233 return True
234 # If the current state is not True, run the command on
235 # the DUT. The new state will be set to whatever the command
236 # produces.
237 return self._host_run_exists(host)
238
Derek Beckett239fdae2021-07-21 14:23:11 -0700239 def _node_type_is_plugged(self, node_type, nodes_info):
240 """Determine if there is any node of node_type plugged.
241
242 This method is used in the AudioLoopbackDongleLabel class, where the
243 call is executed on autotest server. Use get_cras_nodes instead if
244 the call can be executed on Cros device.
245
246 Since Cras only reports the plugged node in GetNodes, we can
247 parse the return value to see if there is any node with the given type.
248 For example, if INTERNAL_MIC is of intereset, the pattern we are
249 looking for is:
250
251 dict entry(
252 string "Type"
253 variant string "INTERNAL_MIC"
254 )
255
256 @param node_type: A str representing node type defined in CRAS_NODE_TYPES.
257 @param nodes_info: A str containing output of command get_nodes_cmd.
258
259 @returns: True if there is any node of node_type plugged. False otherwise.
260
261 """
262 match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type,
263 nodes_info)
264 return True if match else False
265
Gregory Nisbete280ea22019-08-16 17:50:03 -0700266 def _cached_exists(self, host):
267 """Get the state of AudioLoopbackDongle in the data store"""
268 info = host.host_info_store.get()
269 for label in info.labels:
270 if label.startswith(self._NAME):
271 return True
272 return False
273
274 def _host_run_exists(self, host):
275 """Detect presence of audio_loopback_dongle by physically
276 running a command on the DUT."""
Derek Beckett239fdae2021-07-21 14:23:11 -0700277 cras_cmd = ('dbus-send --system --type=method_call --print-reply '
278 '--dest=org.chromium.cras /org/chromium/cras '
279 'org.chromium.cras.Control.GetNodes')
280 nodes_info = host.run(command=cras_cmd, ignore_status=True).stdout
281 if (self._node_type_is_plugged('HEADPHONE', nodes_info)
282 and self._node_type_is_plugged('MIC', nodes_info)):
Otabek Kasimovcefc0d12020-02-07 17:13:52 -0800283 return True
Kevin Chenga2619dc2016-03-28 11:42:08 -0700284 return False
285
Eshwar Narayan871a2c02020-02-06 11:15:24 -0800286 def update_for_task(self, task_name):
287 # This label is stored in the state config, so only repair tasks update
288 # it or when no task name is mentioned.
289 return task_name in (REPAIR_TASK_NAME, '')
290
Kevin Chenga2619dc2016-03-28 11:42:08 -0700291
Otabek Kasimov43185912020-03-11 16:01:52 -0700292class ServoTypeLabel(base_label.StringPrefixLabel):
Otabek Kasimove7565282020-04-14 13:26:12 -0700293 _NAME = servo_constants.SERVO_TYPE_LABEL_PREFIX
Otabek Kasimov43185912020-03-11 16:01:52 -0700294
295 def generate_labels(self, host):
296 info = host.host_info_store.get()
297
298 servo_type = self._get_from_labels(info)
299 if servo_type != '':
Otabek Kasimov1b67c002020-04-15 13:27:38 -0700300 logging.info("Using servo_type: %s from cache!", servo_type)
Otabek Kasimov43185912020-03-11 16:01:52 -0700301 return [servo_type]
302
303 if host.servo is not None:
304 try:
305 servo_type = host.servo.get_servo_version()
306 if servo_type != '':
307 return [servo_type]
Otabek Kasimov1b67c002020-04-15 13:27:38 -0700308 logging.warning('Cannot collect servo_type from servo'
309 ' by `dut-control servo_type`! Please file a bug'
310 ' and inform infra team as we are not expected '
311 ' to reach this point.')
Otabek Kasimov43185912020-03-11 16:01:52 -0700312 except Exception as e:
313 # We don't want fail the label and break DUTs here just
314 # because of servo issue.
315 logging.error("Failed to update servo_type, %s", str(e))
316 return []
317
318 def _get_from_labels(self, info):
319 prefix = self._NAME + ':'
320 for label in info.labels:
321 if label.startswith(prefix):
322 suffix_length = len(prefix)
323 return label[suffix_length:]
324 return ''
325
326 def update_for_task(self, task_name):
327 # This label is stored in the lab config,
328 # only deploy and repair tasks update it
329 # or when no task name is mentioned.
Otabek Kasimove7908f52020-05-05 18:13:33 -0700330 return task_name in (DEPLOY_TASK_NAME, '')
Otabek Kasimov43185912020-03-11 16:01:52 -0700331
332
Xixuan Wu78569d02019-09-15 16:08:25 -0700333def _parse_hwid_labels(hwid_info_list):
334 if len(hwid_info_list) == 0:
335 return hwid_info_list
336
337 res = []
338 # See crbug.com/997816#c7 for details of two potential formats of returns
339 # from HWID server.
340 if isinstance(hwid_info_list[0], dict):
341 # Format of hwid_info:
342 # [{u'name': u'sku', u'value': u'xxx'}, ..., ]
343 for hwid_info in hwid_info_list:
344 value = hwid_info.get('value', '')
345 name = hwid_info.get('name', '')
346 # There should always be a name but just in case there is not.
347 if name:
348 new_label = name if not value else '%s:%s' % (name, value)
349 res.append(new_label)
350 else:
351 # Format of hwid_info:
352 # [<DUTLabel name: 'sku' value: u'xxx'>, ..., ]
353 for hwid_info in hwid_info_list:
354 new_label = str(hwid_info)
355 logging.info('processing hwid label: %s', new_label)
356 res.append(new_label)
357
358 return res
359
360
Kevin Chenga2619dc2016-03-28 11:42:08 -0700361CROS_LABELS = [
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800362 AudioLoopbackDongleLabel(), #STATECONFIG
Shijin Abraham76bc1db2020-03-06 10:52:10 -0800363 BluetoothPeerLabel(), #STATECONFIG
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800364 ChameleonConnectionLabel(), #LABCONFIG
365 ChameleonLabel(), #STATECONFIG
Kevin Chenga2619dc2016-03-28 11:42:08 -0700366 common_label.OSLabel(),
Eshwar Narayanf46904c2020-02-11 17:57:31 -0800367 DeviceSkuLabel(), #LABCONFIG
Otabek Kasimov43185912020-03-11 16:01:52 -0700368 ServoTypeLabel(), #LABCONFIG
Xixuan Wu457b4ac2020-03-02 14:39:08 -0800369 # Temporarily add back as there's no way to reference cr50 configs.
370 # See crbug.com/1057145 for the root cause.
371 # See crbug.com/1057719 for future tracking.
372 Cr50Label(),
373 Cr50ROKeyidLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700374]
Garry Wange4b6d6e2019-06-17 17:08:46 -0700375
376LABSTATION_LABELS = [
Garry Wange4b6d6e2019-06-17 17:08:46 -0700377 common_label.OSLabel(),
378]