blob: 86dd3ce1688fd9d3f8e333687e5eac99bbc88ec2 [file] [log] [blame]
Wei-Yu Chenad55cb82022-02-15 20:07:01 +08001# SPDX-FileCopyrightText: 2020 The Magma Authors.
2# SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org>
3#
4# SPDX-License-Identifier: BSD-3-Clause
Wei-Yu Chen49950b92021-11-08 19:19:18 +08005
6import json
7from typing import Any, List
8
9from data_models.data_model import DataModel
10from data_models.data_model_parameters import ParameterName
Wei-Yu Chen8d064162022-05-27 21:06:55 +080011
12from collections import namedtuple
13
14from lte_utils import DuplexMode, map_earfcndl_to_band_earfcnul_mode
15
Wei-Yu Chen49950b92021-11-08 19:19:18 +080016from exceptions import ConfigurationError
17from logger import EnodebdLogger as logger
18
19
Wei-Yu Chen8d064162022-05-27 21:06:55 +080020SingleEnodebConfig = namedtuple(
21 'SingleEnodebConfig',
22 [
23 'earfcndl', 'subframe_assignment',
24 'special_subframe_pattern',
25 'pci', 'plmnid_list', 'tac',
26 'bandwidth_mhz', 'cell_id',
27 'allow_enodeb_transmit',
28 'mme_address', 'mme_port',
29 ],
30)
31
Wei-Yu Chen49950b92021-11-08 19:19:18 +080032class EnodebConfiguration():
33 """
34 This represents the data model configuration for a single
35 eNodeB device. This can correspond to either the current configuration
36 of the device, or what configuration we desire to have for the device.
37 """
38
39 def __init__(self, data_model: DataModel) -> None:
40 """
41 The fields initialized in the constructor here should be enough to
42 track state across any data model configuration.
43
44 Most objects for eNodeB data models cannot be added or deleted.
45 For those objects, we just track state with a simple mapping from
46 parameter name to value.
47
48 For objects which can be added/deleted, we track them separately.
49 """
50
51 # DataModel
52 self._data_model = data_model
53
54 # Dict[ParameterName, Any]
55 self._param_to_value = {}
56
57 # Dict[ParameterName, Dict[ParameterName, Any]]
58 self._numbered_objects = {}
59 # If adding a PLMN object, then you would set something like
60 # self._numbered_objects['PLMN_1'] = {'PLMN_1_ENABLED': True}
61
62 @property
63 def data_model(self) -> DataModel:
64 """
65 The data model configuration is tied to a single data model
66 """
67 return self._data_model
68
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +080069 def __str__(self) -> str:
70 return str(self._param_to_value)
71
Wei-Yu Chen49950b92021-11-08 19:19:18 +080072 def get_parameter_names(self) -> List[ParameterName]:
73 """
74 Returns: list of ParameterName
75 """
76 return list(self._param_to_value.keys())
77
78 def has_parameter(self, param_name: ParameterName) -> bool:
79 return param_name in self._param_to_value
80
81 def get_parameter(self, param_name: ParameterName) -> Any:
82 """
83 Args:
84 param_name: ParameterName
85 Returns:
86 Any, value of the parameter, formatted to be understood by enodebd
87 """
88 self._assert_param_in_model(param_name)
89 return self._param_to_value[param_name]
90
91 def set_parameter(
92 self,
93 param_name: ParameterName,
94 value: Any,
95 ) -> None:
96 """
97 Args:
98 param_name: the parameter name to configure
99 value: the value to set, formatted to be understood by enodebd
100 """
101 self._assert_param_in_model(param_name)
102 self._param_to_value[param_name] = value
103
Wei-Yu Chen8d064162022-05-27 21:06:55 +0800104 def set_parameter_if_present(self, parameter_name: ParameterName, value: Any) -> None:
105 """
106 Args:
107 param_name: the parameter name to configure
108 value: the value to set, formatted to be understood by enodebd
109 """
110
111 trparam_model = self.data_model
112 tr_param = trparam_model.get_parameter(parameter_name)
113 if tr_param is not None:
114 self._param_to_value[parameter_name] = value
115
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800116 def delete_parameter(self, param_name: ParameterName) -> None:
117 del self._param_to_value[param_name]
118
119 def get_object_names(self) -> List[ParameterName]:
120 return list(self._numbered_objects.keys())
121
122 def has_object(self, param_name: ParameterName) -> bool:
123 """
124 Args:
125 param_name: The ParameterName of the object
126 Returns: True if set in configuration
127 """
128 self._assert_param_in_model(param_name)
129 return param_name in self._numbered_objects
130
131 def add_object(self, param_name: ParameterName) -> None:
132 if param_name in self._numbered_objects:
133 raise ConfigurationError("Configuration already has object")
134 self._numbered_objects[param_name] = {}
135
136 def delete_object(self, param_name: ParameterName) -> None:
137 if param_name not in self._numbered_objects:
138 raise ConfigurationError("Configuration does not have object")
139 del self._numbered_objects[param_name]
140
141 def get_parameter_for_object(
142 self,
143 param_name: ParameterName,
144 object_name: ParameterName,
145 ) -> Any:
146 return self._numbered_objects[object_name].get(param_name)
147
148 def set_parameter_for_object(
149 self,
150 param_name: ParameterName,
151 value: Any,
152 object_name: ParameterName,
153 ) -> None:
154 """
155 Args:
156 param_name: the parameter name to configure
157 value: the value to set, formatted to be understood by enodebd
158 object_name: ParameterName of object
159 """
160 self._assert_param_in_model(object_name)
161 self._assert_param_in_model(param_name)
162 self._numbered_objects[object_name][param_name] = value
163
164 def get_parameter_names_for_object(
165 self,
166 object_name: ParameterName,
167 ) -> List[ParameterName]:
168 return list(self._numbered_objects[object_name].keys())
169
170 def get_debug_info(self) -> str:
171 debug_info = 'Param values: {}, \n Object values: {}'
172 return debug_info.format(
173 json.dumps(self._param_to_value, indent=2),
174 json.dumps(
175 self._numbered_objects,
176 indent=2,
177 ),
178 )
179
180 def _assert_param_in_model(self, param_name: ParameterName) -> None:
181 trparam_model = self.data_model
182 tr_param = trparam_model.get_parameter(param_name)
183 if tr_param is None:
184 logger.warning('Parameter <%s> not defined in model', param_name)
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800185 raise ConfigurationError("Parameter %s not defined in model." % param_name)
Wei-Yu Chen8d064162022-05-27 21:06:55 +0800186
187 def check_desired_configuration(self, current_config, desired_config: dict) -> bool:
188 def config_assert(condition: bool, message: str = None) -> None:
189 """ To be used in place of 'assert' so that ConfigurationError is raised
190 for all config-related exceptions. """
191 if not condition:
192 raise ConfigurationError(message)
193
194 # _set_earfcn_freq_band_mode
195 # Originally:
196 # mconfig: loaded from proto definiation
197 # service_config: loaded from mconfig
198 # device_config: retrieved configuration from enodeb
199 # data_model: defined in device parameter
200
201 # device_config = config loaded from enodeb, desired_config = config loaded from file
202
203 # _check_earfcn_freqw_band_mode
204 try:
205 band, duplex_mode, _ = map_earfcndl_to_band_earfcnul_mode(desired_config["earfcn_downlink1"])
206 except ValueError as err:
207 raise ConfigurationError(err)
208
209 if current_config.has_parameter(ParameterName.DUPLEX_MODE_CAPABILITY):
210 duplex_capability = current_config.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
211 if duplex_mode == DuplexMode.TDD and duplex_capability != "TDDMode":
212 raise ConfigurationError("Duplex mode TDD is not supported by eNodeB")
213 elif duplex_mode == DuplexMode.FDD and duplex_capability != "FDDMode":
214 raise ConfigurationError("Duplex mode FDD is not supported by eNodeB")
215 elif duplex_mode not in [DuplexMode.TDD, DuplexMode.FDD]:
216 raise ConfigurationError("Invalid duplex mode")
217
218 if current_config.has_parameter(ParameterName.BAND_CAPABILITY):
219 band_capability = current_config.get_parameter(ParameterName.BAND_CAPABILITY).split(',')
220 if str(band) not in band_capability:
221 logger.warning("Band %d not in capability list %s", band, band_capability)
222
223 # _check_tdd_subframe_config
224 config_assert(
225 desired_config["subframe_assignment"] in range(0, 7),
226 "Invalid TDD special subframe assignment (%d)" % desired_config["subframe_assignment"],
227 )
228 config_assert(
229 desired_config["special_subframe_pattern"] in range(0, 10),
230 "Invalid TDD special subframe pattern (%d)" % desired_config["special_subframe_pattern"],
231 )
232
233 # _check_plmnids_tac
234 for char in str(desired_config["plmn_list"]):
235 config_assert(char in "0123456789, ", "Invalid PLMNID (%s)" % desired_config["plmn_list"])
236
237 # TODO - add support for multiple PLMNIDs
238 plmnid_list = str(desired_config["plmn_list"]).split(",")
239 config_assert(len(plmnid_list) == 1, "Only 1 PLMNID is supported")
240 config_assert(len(plmnid_list[0]) <= 6, "PLMNID must be length <= 6 (%s)" % plmnid_list[0])
241
242 # _check_s1_connection_configuration
243 config_assert(type(desired_config["mme_address"]) is str, "Invalid MME address")
244 config_assert(type(desired_config["mme_port"]) is int, "Invalid MME port")
245
246 def apply_desired_configuration(self, current_config, desired_config: SingleEnodebConfig) -> None:
247
248 # _set_earfcn_freq_band_mode
249 self.set_parameter(ParameterName.EARFCNDL, desired_config["earfcn_downlink1"])
250 band, duplex_mode, _ = map_earfcndl_to_band_earfcnul_mode(desired_config["earfcn_downlink1"])
251 if duplex_mode == DuplexMode.FDD:
252 self.set_parameter(ParameterName.EARFCNUL, desired_config["earfcn_uplink1"])
253 self.set_parameter_if_present(ParameterName.BAND, band)
254
255 # _set_tdd_subframe_config
256 if (current_config.has_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
257 and current_config.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY) == "TDDMode"):
258 self.set_parameter(ParameterName.SUBFRAME_ASSIGNMENT, desired_config["subframe_assignment"])
259 self.set_parameter(ParameterName.SPECIAL_SUBFRAME_PATTERN, desired_config["special_subframe_pattern"])
260
261 # _set_plmnids_tac
262 plmnid_list = str(desired_config["plmn_list"]).split(",")
263 for i in range(1, 2):
264 object_name = ParameterName.PLMN_N % i
265 enable_plmn = i == 1
266 self.add_object(object_name)
267 self.set_parameter_for_object(ParameterName.PLMN_N_ENABLE % i, enable_plmn, object_name)
268 if enable_plmn:
269 self.set_parameter_for_object(ParameterName.PLMN_N_CELL_RESERVED % i, False, object_name)
270 self.set_parameter_for_object(ParameterName.PLMN_N_PRIMARY % i, enable_plmn, object_name)
271 self.set_parameter_for_object(ParameterName.PLMN_N_PLMNID % i, plmnid_list[i - 1], object_name)
272 self.set_parameter(ParameterName.TAC1, desired_config["tac1"])
273
274 # _set_bandwidth
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +0800275 # FIXME: The DL_BANDWIDTH update request wasn't able to be accepted by enodeb
Wei-Yu Chen8d064162022-05-27 21:06:55 +0800276 self.set_parameter(ParameterName.DL_BANDWIDTH, desired_config["downlink_bandwidth"])
277 self.set_parameter(ParameterName.UL_BANDWIDTH, desired_config["uplink_bandwidth"])
278
279 # _set_cell_id
280 self.set_parameter(ParameterName.CELL_ID, desired_config["cell_id"])
281
282 # _set_misc_static_params
283 self.set_parameter_if_present(ParameterName.LOCAL_GATEWAY_ENABLE, 0)
284 self.set_parameter_if_present(ParameterName.GPS_ENABLE, True)
285 self.set_parameter_if_present(ParameterName.IP_SEC_ENABLE, False)
286 self.set_parameter_if_present(ParameterName.CELL_RESERVED, False)
287 self.set_parameter_if_present(ParameterName.MME_POOL_ENABLE, False)
288
289 # _set_s1_connection_configuration
290 self.set_parameter(ParameterName.MME_ADDRESS, desired_config["mme_address"])
291 self.set_parameter(ParameterName.MME_PORT, desired_config["mme_port"])
292
293 # enable LTE if we should
294 self.set_parameter(ParameterName.ADMIN_STATE, desired_config["admin_state"])
295
Wei-Yu Chen31ebdb52022-06-27 16:53:11 +0800296 # These parameters are already configured above
Wei-Yu Chen8d064162022-05-27 21:06:55 +0800297 exclude_list = [
298 "earfcn_downlink1", "earfcn_uplink1", "subframe_assignment", "special_subframe_pattern",
299 "plmnid", "tac1", "downlink_bandwidth", "uplink_bandwidth", "cell_id",
300 "mme_address", "mme_port", "admin_state"
301 ]
302
303 # Configure the additional parameters which are set in enodeb config files
304 for name, value in desired_config.items():
305 if name not in exclude_list:
306 self.set_parameter_if_present(name, value)