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