blob: 43c7163141358125bdb6626a8c8a814a9b0ff332 [file] [log] [blame]
xixuanbea010f2017-03-27 10:10:19 -07001# Copyright 2017 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"""Module of config file readers."""
Xixuan Wuabbaa4c2017-08-23 17:31:49 -07006# pylint: disable=invalid-name
xixuanbea010f2017-03-27 10:10:19 -07007
xixuanbea010f2017-03-27 10:10:19 -07008import collections
Xixuan Wuabbaa4c2017-08-23 17:31:49 -07009import ConfigParser
Xixuan Wu1e42c752019-03-21 13:41:49 -070010import json
Dhanya Ganeshf2394122020-09-22 19:44:21 +000011import logging
xixuanbea010f2017-03-27 10:10:19 -070012import re
13
Xixuan Wueefb21a2019-03-18 15:15:00 -070014import build_utils
xixuanbea010f2017-03-27 10:10:19 -070015
xixuanbea010f2017-03-27 10:10:19 -070016
Xixuan Wuf4a4c882019-03-15 14:48:26 -070017# Config readers.
18Configs = collections.namedtuple(
19 'Configs',
20 [
21 'lab_config',
Xixuan Wuf4a4c882019-03-15 14:48:26 -070022 ])
23
Xixuan Wu26d06e02017-09-20 14:50:28 -070024# The entries of android settings in lab config file.
Xixuan Wu6fb16272017-10-19 13:16:00 -070025ANDROID_SETTINGS = 'ANDROID'
26
27# The entries of CrOS settings in lab config file.
28CROS_SETTINGS = 'CROS'
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070029
Jack Neus8f0edb42022-03-17 20:21:39 +000030# The entries of Rubik boards in rubik board config file.
31RUBIK_SETTINGS = 'RUBIK'
xixuanbea010f2017-03-27 10:10:19 -070032
33def forgive_config_error(func):
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070034 """A decorator making ConfigParser get*() functions return None on fail."""
35 def wrapper(*args, **kwargs):
36 try:
37 return func(*args, **kwargs)
38 except ConfigParser.Error:
39 return None
xixuanbea010f2017-03-27 10:10:19 -070040
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070041 return wrapper
xixuanbea010f2017-03-27 10:10:19 -070042
43
44class ConfigReader(ConfigParser.SafeConfigParser):
Xixuan Wu0a8dbda2020-03-17 15:01:38 -070045 """A SafeConfigReader that returns None on any error in get*()."""
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070046
47 def __init__(self, filename):
48 """Initialize a config reader."""
49 ConfigParser.SafeConfigParser.__init__(self)
50 self.read(filename)
51
52 def read(self, filename):
53 """Read the file indicated by the given filename.
54
55 Args:
56 filename: string for the file name to read.
xixuanbea010f2017-03-27 10:10:19 -070057 """
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070058 self.filename = filename
59 if filename is not None:
60 ConfigParser.SafeConfigParser.read(self, filename)
xixuanbea010f2017-03-27 10:10:19 -070061
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070062 @forgive_config_error
63 def getstring(self, section, option):
64 """Get string value for the given section and option."""
65 return ConfigParser.SafeConfigParser.get(self, section, option)
xixuanbea010f2017-03-27 10:10:19 -070066
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070067 @forgive_config_error
68 def getint(self, section, option):
69 """Get int value for the given section and option."""
70 return ConfigParser.SafeConfigParser.getint(self, section, option)
xixuanbea010f2017-03-27 10:10:19 -070071
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070072 @forgive_config_error
73 def getfloat(self, section, option):
74 """Get float value for the given section and option."""
75 return ConfigParser.SafeConfigParser.getfloat(self, section, option)
xixuanbea010f2017-03-27 10:10:19 -070076
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070077 @forgive_config_error
78 def getboolean(self, section, option):
79 """Get boolean value for the given section and option."""
80 return ConfigParser.SafeConfigParser.getboolean(self, section, option)
xixuanbea010f2017-03-27 10:10:19 -070081
82
xixuanbea010f2017-03-27 10:10:19 -070083class LabConfig(object):
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070084 """Class for reading lab config file for suite scheduler."""
xixuanbea010f2017-03-27 10:10:19 -070085
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070086 def __init__(self, config_reader):
87 """Initialize a LabConfig to read lab info.
xixuanbea010f2017-03-27 10:10:19 -070088
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070089 Args:
Xixuan Wuf4a4c882019-03-15 14:48:26 -070090 config_reader: a ConfigReader to read lab configs.
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070091 """
92 self._config_reader = config_reader
xixuanbea010f2017-03-27 10:10:19 -070093
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070094 def get_android_board_list(self):
95 """Get the android board list from lab config file.
xixuanbea010f2017-03-27 10:10:19 -070096
Xixuan Wuabbaa4c2017-08-23 17:31:49 -070097 Returns:
98 A set of Android boards: (android-angler, android-bullhead, ...)
xixuanbea010f2017-03-27 10:10:19 -070099
Xixuan Wuabbaa4c2017-08-23 17:31:49 -0700100 Raises:
101 ValueError: no android board list is found.
102 """
Dhanya Ganeshc45d3192020-08-06 20:52:12 +0000103 return set()
xixuanbea010f2017-03-27 10:10:19 -0700104
Xixuan Wu6fb16272017-10-19 13:16:00 -0700105 def get_cros_board_list(self):
106 """Get the CrOS board list from lab config file.
107
108 Returns:
109 A set of CrOS boards: (abox_edge, alex, ...)
110
111 Raises:
112 ValueError: no CrOS board list is found.
113 """
114 board_list = self._config_reader.getstring(CROS_SETTINGS, 'board_list')
115 if board_list is None:
116 raise ValueError('Cannot find CrOS board_list in lab config file.')
117
118 android_board_list = self.get_android_board_list()
119 # Filter board like xxxx_1 to xxxx.
120 cros_board_list = set([re.match(r'(.*?)(?:-\d+)?$', board.strip()).group(1)
121 for board in board_list.split(',')])
Dhanya Ganeshf2394122020-09-22 19:44:21 +0000122 logging.info('Found CROS boards in lab_config.ini: %s', str(cros_board_list))
Xixuan Wu6fb16272017-10-19 13:16:00 -0700123 return cros_board_list - android_board_list
124
C Shapiro7f24a002017-12-05 14:25:09 -0700125 def get_cros_model_map(self):
126 """Gets a map of CrOS boards onto a list of their corresponding models.
127
128 Returns:
129 A map of CrOS board names onto a list of models
130 """
Garry Wang6ca42dd2022-02-28 21:54:19 -0800131 return self._get_model_map(CROS_SETTINGS)
132
133 def get_android_model_map(self):
134 """Gets a map of Android boards onto a list of their corresponding models.
135
136 Returns:
137 A map of Android board names onto a list of models
138 """
139 return self._get_model_map(ANDROID_SETTINGS)
140
141 def _get_model_map(self, setting_name):
C Shapiro7f24a002017-12-05 14:25:09 -0700142 result = {}
143 if not self._config_reader:
144 return result
145
Garry Wang6ca42dd2022-02-28 21:54:19 -0800146 encoded_map = self._config_reader.getstring(setting_name, 'model_list')
C Shapiro7f24a002017-12-05 14:25:09 -0700147 if encoded_map is None:
148 return result
149
Shijin Abrahama8c74772020-01-24 09:39:25 -0800150 # Models are read from boardName_modelName/familyName_boardName_modelName.
C Shapiro7f24a002017-12-05 14:25:09 -0700151 for full_model in encoded_map.split(','):
Xixuan Wueefb21a2019-03-18 15:15:00 -0700152 board_name, model_name = build_utils.parse_full_model(full_model)
C Shapiro7f24a002017-12-05 14:25:09 -0700153 if board_name in result:
154 result[board_name].append(model_name)
155 else:
156 result[board_name] = [model_name]
157
158 return result
159
Jack Neus8f0edb42022-03-17 20:21:39 +0000160class RubikConfig(object):
161 """Class for reading the Rubik config file for suite scheduler."""
162
163 def __init__(self, config_reader):
164 """Initialize a RubikConfig to read rubik config.
165
166 Args:
167 config_reader: a ConfigReader to read rubik config.
168 """
169 self._config_reader = config_reader
170
Jack Neus2cbdc432022-06-24 20:21:30 +0000171 def get_stable_rubik_milestones(self):
172 """Get stable rubik milestones from the Rubik config file.
Jack Neus8f0edb42022-03-17 20:21:39 +0000173
174 Returns:
Jack Neus2cbdc432022-06-24 20:21:30 +0000175 A dict mapping build target (e.g. eve) to a stable rubik milestone.
Jack Neus8f0edb42022-03-17 20:21:39 +0000176
177 Raises:
178 ValueError: no build target list is found.
179 """
Jack Neus2cbdc432022-06-24 20:21:30 +0000180 data = self._config_reader.getstring(RUBIK_SETTINGS, 'stable_rubik_milestones')
181 if data is None:
182 raise ValueError('Cannot find stable_rubik_milestones in Rubik config file.')
Jack Neus8f0edb42022-03-17 20:21:39 +0000183
Jack Neus2cbdc432022-06-24 20:21:30 +0000184 data = set([bt.strip() for bt in data.split(',')])
185 mstone_map = {}
186 for entry in data:
187 entry = entry.split('=')
188 mstone_map[entry[0]] = int(entry[1])
189 logging.info('Found stable rubik milestones targets in rubik_config.ini: %s', str(mstone_map))
190 return mstone_map
Jack Neus8f0edb42022-03-17 20:21:39 +0000191
xixuanbea010f2017-03-27 10:10:19 -0700192
Xixuan Wu1e42c752019-03-21 13:41:49 -0700193def config_dump(res):
194 """Dump a config dict to a json string.
195
196 Args:
197 res: A dict object to dump.
198
199 Returns:
200 A json string.
201 """
202 return json.dumps(res,
203 sort_keys=True,
204 indent=4,
205 separators=(',', ': ')) + '\n'
206
207
208def _parse_model_list(models):
209 r"""Parse models of list.
210
211 Args:
212 models: A string, read from lab or migration config file.
213 e.g. '\nreef,\nastronaut,\nrobo360'
214 Returns:
215 A set of string models, e.g. set(['reef', 'astronaut', 'robo360', ...])
216 """
217 return set([re.match(r'(.*?)(?:-\d+)?$', m.strip()).group(1)
218 for m in models.split(',')])
219
220
221def _parse_pool_list(l):
Ned Nguyenb0a377d2020-08-27 08:43:30 -0600222 r"""Parse allowlist/denylist for a suite in migration config file.
Xixuan Wu1e42c752019-03-21 13:41:49 -0700223
224 Args:
225 l: A string, read from migration config file entry
Ned Nguyenb0a377d2020-08-27 08:43:30 -0600226 allowlist or denylist. e.g. '\ncq:reef,\nbvt:peppy...'
Xixuan Wu1e42c752019-03-21 13:41:49 -0700227
228 Returns:
Ned Nguyenb0a377d2020-08-27 08:43:30 -0600229 A list of allowlist/denylist <pool:model> string.
Xixuan Wu1e42c752019-03-21 13:41:49 -0700230 e.g. ['cq:reef', 'bvt:peppy', ...]
231 """
232 if l is None or not l:
233 return []
234 else:
Xixuan Wu0d2ff812019-04-11 14:09:17 -0700235 return list(set([w.strip() for w in l.split('\n') if w.strip()]))
Xixuan Wu1e42c752019-03-21 13:41:49 -0700236
237
238def _get_models_by_pool(model_pool_list, pool):
239 """Get models from a model_pool_list by pool.
240
241 Args:
242 model_pool_list: A list of model_pool string. e.g.
Xixuan Wu0d2ff812019-04-11 14:09:17 -0700243 [(cq, reef), (bvt, peppy), (bvt, link), ...]
Xixuan Wu1e42c752019-03-21 13:41:49 -0700244 pool: A string pool. e.g. 'bvt'
245
246 Returns:
247 A list of string models searched by pool. e.g.
248 ['peppy', 'link', ...]
249 """
Xixuan Wu0d2ff812019-04-11 14:09:17 -0700250 res = set([])
251 for mp in model_pool_list:
252 p, m = _parse_pool_model_key(mp)
253 if p == pool:
254 res.add(m)
Xixuan Wu1e42c752019-03-21 13:41:49 -0700255
Xixuan Wu0d2ff812019-04-11 14:09:17 -0700256 return list(res)
Xixuan Wu1e42c752019-03-21 13:41:49 -0700257
258
259def suite_key(suite_name):
260 return 'suite:%s' % suite_name
261
262
263def model_key(model_name):
264 return 'model:%s' % model_name
265
266
267def pool_key(pool_name):
268 return 'pool:%s' % pool_name
Xixuan Wu0d2ff812019-04-11 14:09:17 -0700269
270
271def _pool_model_key(pool_name, model_name=None):
272 if model_name is None:
273 return '(%s)' % pool_name
274
275 return '(%s, %s)' % (pool_name, model_name)
276
277
278def _parse_pool_model_key(model_pool):
279 return (model_pool[1:-1].split(',')[0].strip(),
280 model_pool[1:-1].split(',')[1].strip())