blob: 0f2e9cd81743a8a77c43e5e11303579148b7d605 [file] [log] [blame]
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001"""
2Copyright 2020 The Magma Authors.
3
4This source code is licensed under the BSD-style license found in the
5LICENSE file in the root directory of this source tree.
6
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12"""
13
14from typing import Any, Callable, Dict, List, Optional, Type
15
16from common.service import MagmaService
17from data_models import transform_for_enb, transform_for_magma
18from data_models.data_model import DataModel, TrParam
19from data_models.data_model_parameters import (
20 ParameterName,
21 TrParameterType,
22)
23from device_config.configuration_init import build_desired_config
24from device_config.enodeb_config_postprocessor import (
25 EnodebConfigurationPostProcessor,
26)
27from device_config.enodeb_configuration import EnodebConfiguration
28from devices.device_utils import EnodebDeviceName
29from logger import EnodebdLogger as logger
30from state_machines.acs_state_utils import (
31 get_all_objects_to_add,
32 get_all_objects_to_delete,
33 get_all_param_values_to_set,
34 get_params_to_get,
35 parse_get_parameter_values_response,
36)
37from state_machines.enb_acs import EnodebAcsStateMachine
38from state_machines.enb_acs_impl import BasicEnodebAcsStateMachine
39from state_machines.enb_acs_states import (
40 AcsMsgAndTransition,
41 AcsReadMsgResult,
42 AddObjectsState,
43 DeleteObjectsState,
44 EnbSendRebootState,
45 EndSessionState,
46 EnodebAcsState,
47 ErrorState,
48 GetParametersState,
49 GetRPCMethodsState,
50 SendGetTransientParametersState,
51 SetParameterValuesState,
52 WaitEmptyMessageState,
53 WaitGetParametersState,
54 WaitInformMRebootState,
55 WaitInformState,
56 WaitRebootResponseState,
57 WaitSetParameterValuesState,
58)
59from tr069 import models
60
61
62class BaicellsQAFBHandler(BasicEnodebAcsStateMachine):
63 def __init__(
64 self,
65 service: MagmaService,
66 ) -> None:
67 self._state_map = {}
68 super().__init__(service=service, use_param_key=False)
69
70 def reboot_asap(self) -> None:
71 self.transition('reboot')
72
73 def is_enodeb_connected(self) -> bool:
74 return not isinstance(self.state, WaitInformState)
75
76 def _init_state_map(self) -> None:
77 self._state_map = {
78 'wait_inform': WaitInformState(self, when_done='get_rpc_methods'),
79 'get_rpc_methods': GetRPCMethodsState(self, when_done='wait_empty', when_skip='get_transient_params'),
80 'wait_empty': WaitEmptyMessageState(self, when_done='get_transient_params'),
81 'get_transient_params': SendGetTransientParametersState(self, when_done='wait_get_transient_params'),
82 'wait_get_transient_params': BaicellsQafbWaitGetTransientParametersState(self, when_get='get_params', when_get_obj_params='get_obj_params', when_delete='delete_objs', when_add='add_objs', when_set='set_params', when_skip='end_session'),
83 'get_params': GetParametersState(self, when_done='wait_get_params'),
84 'wait_get_params': WaitGetParametersState(self, when_done='get_obj_params'),
85 'get_obj_params': BaicellsQafbGetObjectParametersState(self, when_delete='delete_objs', when_add='add_objs', when_set='set_params', when_skip='end_session'),
86 'delete_objs': DeleteObjectsState(self, when_add='add_objs', when_skip='set_params'),
87 'add_objs': AddObjectsState(self, when_done='set_params'),
88 'set_params': SetParameterValuesState(self, when_done='wait_set_params'),
89 'wait_set_params': WaitSetParameterValuesState(self, when_done='check_get_params', when_apply_invasive='check_get_params'),
90 'check_get_params': GetParametersState(self, when_done='check_wait_get_params', request_all_params=True),
91 'check_wait_get_params': WaitGetParametersState(self, when_done='end_session'),
92 'end_session': EndSessionState(self),
93 # These states are only entered through manual user intervention
94 'reboot': EnbSendRebootState(self, when_done='wait_reboot'),
95 'wait_reboot': WaitRebootResponseState(self, when_done='wait_post_reboot_inform'),
96 'wait_post_reboot_inform': WaitInformMRebootState(self, when_done='wait_empty', when_timeout='wait_inform'),
97 # The states below are entered when an unexpected message type is
98 # received
99 'unexpected_fault': ErrorState(self, inform_transition_target='wait_inform'),
100 }
101
102 @property
103 def device_name(self) -> str:
104 return EnodebDeviceName.BAICELLS_QAFB
105
106 @property
107 def data_model_class(self) -> Type[DataModel]:
108 return BaicellsQAFBTrDataModel
109
110 @property
111 def config_postprocessor(self) -> EnodebConfigurationPostProcessor:
112 return BaicellsQAFBTrConfigurationInitializer()
113
114 @property
115 def state_map(self) -> Dict[str, EnodebAcsState]:
116 return self._state_map
117
118 @property
119 def disconnected_state_name(self) -> str:
120 return 'wait_inform'
121
122 @property
123 def unexpected_fault_state_name(self) -> str:
124 return 'unexpected_fault'
125
126
127def _get_object_params_to_get(
128 device_cfg: EnodebConfiguration,
129 data_model: DataModel,
130) -> List[ParameterName]:
131 """
132 Returns a list of parameter names for object parameters we don't know the
133 current value of.
134
135 Since there is no parameter for tracking the number of PLMNs, then we
136 make the assumption that if any PLMN object exists, then we've already
137 fetched the object parameter values.
138 """
139 if device_cfg.has_object(ParameterName.PLMN_N % 1):
140 return []
141
142 names = []
143 num_plmns = data_model.get_num_plmns()
144 obj_to_params = data_model.get_numbered_param_names()
145 for i in range(1, num_plmns + 1):
146 obj_name = ParameterName.PLMN_N % i
147 desired = obj_to_params[obj_name]
148 names = names + desired
149 return names
150
151
152class BaicellsQafbWaitGetTransientParametersState(EnodebAcsState):
153 """
154 Periodically read eNodeB status. Note: keep frequency low to avoid
155 backing up large numbers of read operations if enodebd is busy
156 """
157
158 def __init__(
159 self,
160 acs: EnodebAcsStateMachine,
161 when_get: str,
162 when_get_obj_params: str,
163 when_delete: str,
164 when_add: str,
165 when_set: str,
166 when_skip: str,
167 ):
168 super().__init__()
169 self.acs = acs
170 self.done_transition = when_get
171 self.get_obj_params_transition = when_get_obj_params
172 self.rm_obj_transition = when_delete
173 self.add_obj_transition = when_add
174 self.set_transition = when_set
175 self.skip_transition = when_skip
176
177 def read_msg(self, message: Any) -> AcsReadMsgResult:
178 """ Process GetParameterValuesResponse """
179 if not isinstance(message, models.GetParameterValuesResponse):
180 return AcsReadMsgResult(False, None)
181 # Current values of the fetched parameters
182 name_to_val = parse_get_parameter_values_response(
183 self.acs.data_model,
184 message,
185 )
186 logger.debug('Received Parameters: %s', str(name_to_val))
187
188 # Update device configuration
189 for name in name_to_val:
190 magma_value = \
191 self.acs.data_model.transform_for_magma(name, name_to_val[name])
192 self.acs.device_cfg.set_parameter(name, magma_value)
193
194 return AcsReadMsgResult(True, self.get_next_state())
195
196 def get_next_state(self) -> str:
197 should_get_params = \
198 len(
199 get_params_to_get(
200 self.acs.device_cfg,
201 self.acs.data_model,
202 ),
203 ) > 0
204 if should_get_params:
205 return self.done_transition
206 should_get_obj_params = \
207 len(
208 _get_object_params_to_get(
209 self.acs.device_cfg,
210 self.acs.data_model,
211 ),
212 ) > 0
213 if should_get_obj_params:
214 return self.get_obj_params_transition
215 elif len(
216 get_all_objects_to_delete(
217 self.acs.desired_cfg,
218 self.acs.device_cfg,
219 ),
220 ) > 0:
221 return self.rm_obj_transition
222 elif len(
223 get_all_objects_to_add(
224 self.acs.desired_cfg,
225 self.acs.device_cfg,
226 ),
227 ) > 0:
228 return self.add_obj_transition
229 return self.skip_transition
230
231 def state_description(self) -> str:
232 return 'Getting transient read-only parameters'
233
234
235class BaicellsQafbGetObjectParametersState(EnodebAcsState):
236 """
237 Get information on parameters belonging to objects that can be added or
238 removed from the configuration.
239
240 Baicells QAFB will report a parameter value as None if it does not exist
241 in the data model, rather than replying with a Fault message like most
242 eNB devices.
243 """
244
245 def __init__(
246 self,
247 acs: EnodebAcsStateMachine,
248 when_delete: str,
249 when_add: str,
250 when_set: str,
251 when_skip: str,
252 ):
253 super().__init__()
254 self.acs = acs
255 self.rm_obj_transition = when_delete
256 self.add_obj_transition = when_add
257 self.set_params_transition = when_set
258 self.skip_transition = when_skip
259
260 def get_msg(self, message: Any) -> AcsMsgAndTransition:
261 """ Respond with GetParameterValuesRequest """
262 names = _get_object_params_to_get(
263 self.acs.device_cfg,
264 self.acs.data_model,
265 )
266
267 # Generate the request
268 request = models.GetParameterValues()
269 request.ParameterNames = models.ParameterNames()
270 request.ParameterNames.arrayType = 'xsd:string[%d]' \
271 % len(names)
272 request.ParameterNames.string = []
273 for name in names:
274 path = self.acs.data_model.get_parameter(name).path
275 request.ParameterNames.string.append(path)
276
277 return AcsMsgAndTransition(request, None)
278
279 def read_msg(self, message: Any) -> AcsReadMsgResult:
280 """
281 Process GetParameterValuesResponse
282
283 Object parameters that have a reported value of None indicate that
284 the object is not in the eNB's configuration. Most eNB devices will
285 reply with a Fault message if we try to get values of parameters that
286 don't exist on the data model, so this is an idiosyncrasy of Baicells
287 QAFB.
288 """
289 if not isinstance(message, models.GetParameterValuesResponse):
290 return AcsReadMsgResult(False, None)
291
292 path_to_val = {}
293 for param_value_struct in message.ParameterList.ParameterValueStruct:
294 path_to_val[param_value_struct.Name] = \
295 param_value_struct.Value.Data
296
297 logger.debug('Received object parameters: %s', str(path_to_val))
298
299 num_plmns = self.acs.data_model.get_num_plmns()
300 for i in range(1, num_plmns + 1):
301 obj_name = ParameterName.PLMN_N % i
302 obj_to_params = self.acs.data_model.get_numbered_param_names()
303 param_name_list = obj_to_params[obj_name]
304 for name in param_name_list:
305 path = self.acs.data_model.get_parameter(name).path
306 if path in path_to_val:
307 value = path_to_val[path]
308 if value is None:
309 continue
310 if obj_name not in self.acs.device_cfg.get_object_names():
311 self.acs.device_cfg.add_object(obj_name)
312 magma_value = \
313 self.acs.data_model.transform_for_magma(name, value)
314 self.acs.device_cfg.set_parameter_for_object(
315 name,
316 magma_value,
317 obj_name,
318 )
319
320 # Now we have enough information to build the desired configuration
321 if self.acs.desired_cfg is None:
322 self.acs.desired_cfg = build_desired_config(
323 self.acs.mconfig,
324 self.acs.service_config,
325 self.acs.device_cfg,
326 self.acs.data_model,
327 self.acs.config_postprocessor,
328 )
329
330 if len(
331 get_all_objects_to_delete(
332 self.acs.desired_cfg,
333 self.acs.device_cfg,
334 ),
335 ) > 0:
336 return AcsReadMsgResult(True, self.rm_obj_transition)
337 elif len(
338 get_all_objects_to_add(
339 self.acs.desired_cfg,
340 self.acs.device_cfg,
341 ),
342 ) > 0:
343 return AcsReadMsgResult(True, self.add_obj_transition)
344 elif len(
345 get_all_param_values_to_set(
346 self.acs.desired_cfg,
347 self.acs.device_cfg,
348 self.acs.data_model,
349 ),
350 ) > 0:
351 return AcsReadMsgResult(True, self.set_params_transition)
352 return AcsReadMsgResult(True, self.skip_transition)
353
354 def state_description(self) -> str:
355 return 'Getting object parameters'
356
357
358class BaicellsQAFBTrDataModel(DataModel):
359 """
360 Class to represent relevant data model parameters from TR-196/TR-098.
361 This class is effectively read-only.
362
363 This model specifically targets Qualcomm-based BaiCells units running
364 QAFB firmware.
365
366 These models have these idiosyncrasies (on account of running TR098):
367
368 - Parameter content root is different (InternetGatewayDevice)
369 - GetParameter queries with a wildcard e.g. InternetGatewayDevice. do
370 not respond with the full tree (we have to query all parameters)
371 - MME status is not exposed - we assume the MME is connected if
372 the eNodeB is transmitting (OpState=true)
373 - Parameters such as band capability/duplex config
374 are rooted under `boardconf.` and not the device config root
375 - Parameters like Admin state, CellReservedForOperatorUse,
376 Duplex mode, DL bandwidth and Band capability have different
377 formats from Intel-based Baicells units, necessitating,
378 formatting before configuration and transforming values
379 read from eNodeB state.
380 - Num PLMNs is not reported by these units
381 """
382 # Mapping of TR parameter paths to aliases
383 DEVICE_PATH = 'InternetGatewayDevice.'
384 FAPSERVICE_PATH = DEVICE_PATH + 'Services.FAPService.1.'
385 EEPROM_PATH = 'boardconf.status.eepromInfo.'
386 PARAMETERS = {
387 # Top-level objects
388 ParameterName.DEVICE: TrParam(DEVICE_PATH, True, TrParameterType.OBJECT, False),
389 ParameterName.FAP_SERVICE: TrParam(FAPSERVICE_PATH, True, TrParameterType.OBJECT, False),
390
391 # Qualcomm units do not expose MME_Status (We assume that the eNB is broadcasting state is connected to the MME)
392 ParameterName.MME_STATUS: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.X_QUALCOMM_FAPControl.OpState', True, TrParameterType.BOOLEAN, False),
393 ParameterName.GPS_LAT: TrParam(DEVICE_PATH + 'FAP.GPS.latitude', True, TrParameterType.STRING, False),
394 ParameterName.GPS_LONG: TrParam(DEVICE_PATH + 'FAP.GPS.longitude', True, TrParameterType.STRING, False),
395 ParameterName.SW_VERSION: TrParam(DEVICE_PATH + 'DeviceInfo.SoftwareVersion', True, TrParameterType.STRING, False),
396 ParameterName.SERIAL_NUMBER: TrParam(DEVICE_PATH + 'DeviceInfo.SerialNumber', True, TrParameterType.STRING, False),
397
398 # Capabilities
399 ParameterName.DUPLEX_MODE_CAPABILITY: TrParam(EEPROM_PATH + 'div_multiple', True, TrParameterType.STRING, False),
400 ParameterName.BAND_CAPABILITY: TrParam(EEPROM_PATH + 'work_mode', True, TrParameterType.STRING, False),
401
402 # RF-related parameters
403 ParameterName.EARFCNDL: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.RAN.RF.EARFCNDL', True, TrParameterType.INT, False),
404 ParameterName.PCI: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.RAN.RF.PhyCellID', True, TrParameterType.INT, False),
405 ParameterName.DL_BANDWIDTH: TrParam(DEVICE_PATH + 'Services.RfConfig.1.RfCarrierCommon.carrierBwMhz', True, TrParameterType.INT, False),
406 ParameterName.SUBFRAME_ASSIGNMENT: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.RAN.PHY.TDDFrame.SubFrameAssignment', True, 'bool', False),
407 ParameterName.SPECIAL_SUBFRAME_PATTERN: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.RAN.PHY.TDDFrame.SpecialSubframePatterns', True, TrParameterType.INT, False),
408 ParameterName.CELL_ID: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.RAN.Common.CellIdentity', True, TrParameterType.UNSIGNED_INT, False),
409
410 # Other LTE parameters
411 ParameterName.ADMIN_STATE: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.X_QUALCOMM_FAPControl.AdminState', False, TrParameterType.STRING, False),
412 ParameterName.OP_STATE: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.X_QUALCOMM_FAPControl.OpState', True, TrParameterType.BOOLEAN, False),
413 ParameterName.RF_TX_STATUS: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.X_QUALCOMM_FAPControl.OpState', True, TrParameterType.BOOLEAN, False),
414
415 # Core network parameters
416 ParameterName.MME_IP: TrParam(FAPSERVICE_PATH + 'FAPControl.LTE.Gateway.S1SigLinkServerList', True, TrParameterType.STRING, False),
417 ParameterName.MME_PORT: TrParam(FAPSERVICE_PATH + 'FAPControl.LTE.Gateway.S1SigLinkPort', True, TrParameterType.INT, False),
418 # This parameter is standard but doesn't exist
419 # ParameterName.NUM_PLMNS: TrParam(FAPSERVICE_PATH + 'CellConfig.LTE.EPC.PLMNListNumberOfEntries', True, TrParameterType.INT, False),
420 ParameterName.TAC: TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.EPC.TAC', True, TrParameterType.INT, False),
421 ParameterName.IP_SEC_ENABLE: TrParam('boardconf.ipsec.ipsecConfig.onBoot', False, TrParameterType.BOOLEAN, False),
422
423 # Management server parameters
424 ParameterName.PERIODIC_INFORM_ENABLE: TrParam(DEVICE_PATH + 'ManagementServer.PeriodicInformEnable', False, TrParameterType.BOOLEAN, False),
425 ParameterName.PERIODIC_INFORM_INTERVAL: TrParam(DEVICE_PATH + 'ManagementServer.PeriodicInformInterval', False, TrParameterType.INT, False),
426
427 # Performance management parameters
428 ParameterName.PERF_MGMT_ENABLE: TrParam(FAPSERVICE_PATH + 'CellConfig.1.X_QUALCOMM_PerfMgmt.Config.Enable', False, TrParameterType.BOOLEAN, False),
429 ParameterName.PERF_MGMT_UPLOAD_INTERVAL: TrParam(DEVICE_PATH + 'FAP.PerfMgmt.Config.PeriodicUploadInterval', False, TrParameterType.INT, False),
430 ParameterName.PERF_MGMT_UPLOAD_URL: TrParam(DEVICE_PATH + 'FAP.PerfMgmt.Config.URL', False, TrParameterType.STRING, False),
431 }
432
433 NUM_PLMNS_IN_CONFIG = 6
434 TRANSFORMS_FOR_ENB = {
435 ParameterName.CELL_BARRED: transform_for_enb.invert_cell_barred,
436 }
437 for i in range(1, NUM_PLMNS_IN_CONFIG + 1):
438 TRANSFORMS_FOR_ENB[ParameterName.PLMN_N_CELL_RESERVED % i] = transform_for_enb.cell_reserved
439 PARAMETERS[ParameterName.PLMN_N % i] = TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.EPC.PLMNList.%d.' % i, True, TrParameterType.STRING, False)
440 PARAMETERS[ParameterName.PLMN_N_CELL_RESERVED % i] = TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.EPC.PLMNList.%d.CellReservedForOperatorUse' % i, True, TrParameterType.STRING, False)
441 PARAMETERS[ParameterName.PLMN_N_ENABLE % i] = TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.EPC.PLMNList.%d.Enable' % i, True, TrParameterType.BOOLEAN, False)
442 PARAMETERS[ParameterName.PLMN_N_PRIMARY % i] = TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.EPC.PLMNList.%d.IsPrimary' % i, True, TrParameterType.BOOLEAN, False)
443 PARAMETERS[ParameterName.PLMN_N_PLMNID % i] = TrParam(FAPSERVICE_PATH + 'CellConfig.1.LTE.EPC.PLMNList.%d.PLMNID' % i, True, TrParameterType.STRING, False)
444
445 TRANSFORMS_FOR_ENB[ParameterName.ADMIN_STATE] = transform_for_enb.admin_state
446 TRANSFORMS_FOR_MAGMA = {
447 # We don't set these parameters
448 ParameterName.BAND_CAPABILITY: transform_for_magma.band_capability,
449 ParameterName.DUPLEX_MODE_CAPABILITY: transform_for_magma.duplex_mode,
450 }
451
452 @classmethod
453 def get_parameter(cls, param_name: ParameterName) -> Optional[TrParam]:
454 return cls.PARAMETERS.get(param_name)
455
456 @classmethod
457 def _get_magma_transforms(
458 cls,
459 ) -> Dict[ParameterName, Callable[[Any], Any]]:
460 return cls.TRANSFORMS_FOR_MAGMA
461
462 @classmethod
463 def _get_enb_transforms(cls) -> Dict[ParameterName, Callable[[Any], Any]]:
464 return cls.TRANSFORMS_FOR_ENB
465
466 @classmethod
467 def get_load_parameters(cls) -> List[ParameterName]:
468 """
469 Load all the parameters instead of a subset.
470 """
471 return list(cls.PARAMETERS.keys())
472
473 @classmethod
474 def get_num_plmns(cls) -> int:
475 return cls.NUM_PLMNS_IN_CONFIG
476
477 @classmethod
478 def get_parameter_names(cls) -> List[ParameterName]:
479 excluded_params = [
480 str(ParameterName.DEVICE),
481 str(ParameterName.FAP_SERVICE),
482 ]
483 names = list(
484 filter(
485 lambda x: (not str(x).startswith('PLMN'))
486 and (str(x) not in excluded_params),
487 cls.PARAMETERS.keys(),
488 ),
489 )
490 return names
491
492 @classmethod
493 def get_numbered_param_names(
494 cls,
495 ) -> Dict[ParameterName, List[ParameterName]]:
496 names = {}
497 for i in range(1, cls.NUM_PLMNS_IN_CONFIG + 1):
498 params = []
499 params.append(ParameterName.PLMN_N_CELL_RESERVED % i)
500 params.append(ParameterName.PLMN_N_ENABLE % i)
501 params.append(ParameterName.PLMN_N_PRIMARY % i)
502 params.append(ParameterName.PLMN_N_PLMNID % i)
503 names[ParameterName.PLMN_N % i] = params
504
505 return names
506
507
508class BaicellsQAFBTrConfigurationInitializer(EnodebConfigurationPostProcessor):
509 def postprocess(self, mconfig: Any, service_cfg: Any, desired_cfg: EnodebConfiguration) -> None:
510 # We don't set this parameter for this device, it should be
511 # auto-configured by the device.
512 desired_cfg.delete_parameter(ParameterName.ADMIN_STATE)
513 return