blob: e9bf9b022dcb85bc61653041604161c0543a8431 [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"""
13import logging
14from typing import Any, Callable, Dict, List, Optional, Type
15
16from common.service import MagmaService
17from data_models import transform_for_magma
18from data_models.data_model import (
19 DataModel,
20 InvalidTrParamPath,
21 TrParam,
22)
23from data_models.data_model_parameters import (
24 ParameterName,
25 TrParameterType,
26)
27from device_config.configuration_init import build_desired_config
28from device_config.enodeb_config_postprocessor import EnodebConfigurationPostProcessor
29from device_config.enodeb_configuration import EnodebConfiguration
30from devices.device_utils import EnodebDeviceName
31from logger import EnodebdLogger
32from state_machines.acs_state_utils import (
33 get_all_objects_to_add,
34 get_all_objects_to_delete,
35 get_all_param_values_to_set,
36 get_params_to_get,
37 parse_get_parameter_values_response,
38)
39from state_machines.enb_acs import EnodebAcsStateMachine
40from state_machines.enb_acs_impl import BasicEnodebAcsStateMachine
41from state_machines.enb_acs_states import (
42 AcsMsgAndTransition,
43 AcsReadMsgResult,
44 AddObjectsState,
45 DeleteObjectsState,
46 EnbSendRebootState,
47 EndSessionState,
48 EnodebAcsState,
49 ErrorState,
50 GetParametersState,
51 SetParameterValuesState,
52 WaitGetParametersState,
53 WaitInformMRebootState,
54 WaitInformState,
55 WaitRebootResponseState,
56 WaitSetParameterValuesState,
57)
58from tr069 import models
59
60
61class SASParameters:
62 """ Class modeling the SAS parameters and their TR path"""
63
64 # SAS parameters for FreedomFiOne
65 FAP_CONTROL = "Device.Services.FAPService.1.FAPControl."
66 FAPSERVICE_PATH = "Device.Services.FAPService.1."
67
68 # Sas management parameters
69 SAS_ENABLE = "sas_enabled"
70 SAS_SERVER_URL = "sas_server_url"
71 SAS_UID = "sas_uid"
72 SAS_CATEGORY = "sas_category"
73 SAS_CHANNEL_TYPE = "sas_channel_type"
74 SAS_CERT_SUBJECT = "sas_cert_subject"
75 SAS_IC_GROUP_ID = "sas_icg_group_id"
76 SAS_LOCATION = "sas_location"
77 SAS_HEIGHT_TYPE = "sas_height_type"
78 SAS_CPI_ENABLE = "sas_cpi_enable"
79 SAS_CPI_IPE = "sas_cpi_ipe" # Install param supplied enable
80 FREQ_BAND_1 = "freq_band_1"
81 FREQ_BAND_2 = "freq_band_2"
82 # For CBRS radios we set this to the limit and the SAS can reduce the
83 # power if needed.
84 TX_POWER_CONFIG = "tx_power_config"
85
86 SAS_PARAMETERS = {
87 SAS_ENABLE: TrParam(
88 FAP_CONTROL + "LTE.X_000E8F_SAS.Enable",
89 is_invasive=False,
90 type=TrParameterType.BOOLEAN,
91 is_optional=False,
92 ),
93 SAS_SERVER_URL: TrParam(
94 FAP_CONTROL + "LTE.X_000E8F_SAS.Server",
95 is_invasive=False,
96 type=TrParameterType.STRING,
97 is_optional=False,
98 ),
99 SAS_UID: TrParam(
100 FAP_CONTROL + "LTE.X_000E8F_SAS.UserContactInformation",
101 is_invasive=False,
102 type=TrParameterType.STRING,
103 is_optional=False,
104 ),
105 SAS_CATEGORY: TrParam(
106 FAP_CONTROL + "LTE.X_000E8F_SAS.Category",
107 is_invasive=False,
108 type=TrParameterType.STRING,
109 is_optional=False,
110 ),
111 SAS_CHANNEL_TYPE: TrParam(
112 FAP_CONTROL + "LTE.X_000E8F_SAS.ProtectionLevel",
113 is_invasive=False,
114 type=TrParameterType.STRING,
115 is_optional=False,
116 ),
117 SAS_CERT_SUBJECT: TrParam(
118 FAP_CONTROL + "LTE.X_000E8F_SAS.CertSubject",
119 is_invasive=False,
120 type=TrParameterType.STRING,
121 is_optional=False,
122 ),
123 # SAS_IC_GROUP_ID: TrParam(
124 # FAP_CONTROL + 'LTE.X_000E8F_SAS.ICGGroupId', is_invasive=False,
125 # type=TrParameterType.STRING, False),
126 SAS_LOCATION: TrParam(
127 FAP_CONTROL + "LTE.X_000E8F_SAS.Location",
128 is_invasive=False,
129 type=TrParameterType.STRING,
130 is_optional=False,
131 ),
132 SAS_HEIGHT_TYPE: TrParam(
133 FAP_CONTROL + "LTE.X_000E8F_SAS.HeightType",
134 is_invasive=False,
135 type=TrParameterType.STRING,
136 is_optional=False,
137 ),
138 SAS_CPI_ENABLE: TrParam(
139 FAP_CONTROL + "LTE.X_000E8F_SAS.CPIEnable",
140 is_invasive=False,
141 type=TrParameterType.BOOLEAN,
142 is_optional=False,
143 ),
144 SAS_CPI_IPE: TrParam(
145 FAP_CONTROL + "LTE.X_000E8F_SAS.CPIInstallParamSuppliedEnable",
146 is_invasive=False,
147 type=TrParameterType.BOOLEAN,
148 is_optional=False,
149 ),
150 FREQ_BAND_1: TrParam(
151 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.FreqBandIndicator",
152 is_invasive=False,
153 type=TrParameterType.UNSIGNED_INT,
154 is_optional=False,
155 ),
156 FREQ_BAND_2: TrParam(
157 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_000E8F_FreqBandIndicator2",
158 is_invasive=False,
159 type=TrParameterType.UNSIGNED_INT,
160 is_optional=False,
161 ),
162 TX_POWER_CONFIG: TrParam(
163 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_000E8F_TxPowerConfig",
164 is_invasive=True,
165 type=TrParameterType.INT,
166 is_optional=False,
167 ),
168 }
169
170
171class StatusParameters:
172 """
173 Stateful class that converts eNB status to Magma understood status.
174 FreedomFiOne has many status params that interact with each other.
175 This class maintains the business logic of parsing these status params
176 and converting it to Magma understood fields.
177 """
178
179 STATUS_PATH = "Device.X_000E8F_DeviceFeature.X_000E8F_NEStatus."
180
181 # Status parameters
182 DEFAULT_GW = "defaultGW"
183 SYNC_STATUS = "syncStatus"
184 SAS_STATUS = "sasStatus"
185 ENB_STATUS = "enbStatus"
186 GPS_SCAN_STATUS = "gpsScanStatus"
187
188 STATUS_PARAMETERS = {
189 # Status nodes
190 DEFAULT_GW: TrParam(
191 STATUS_PATH + "X_000E8F_DEFGW_Status",
192 is_invasive=False,
193 type=TrParameterType.STRING,
194 is_optional=False,
195 ),
196 SYNC_STATUS: TrParam(
197 STATUS_PATH + "X_000E8F_Sync_Status",
198 is_invasive=False,
199 type=TrParameterType.STRING,
200 is_optional=False,
201 ),
202 SAS_STATUS: TrParam(
203 STATUS_PATH + "X_000E8F_SAS_Status",
204 is_invasive=False,
205 type=TrParameterType.STRING,
206 is_optional=False,
207 ),
208 ENB_STATUS: TrParam(
209 STATUS_PATH + "X_000E8F_eNB_Status",
210 is_invasive=False,
211 type=TrParameterType.STRING,
212 is_optional=False,
213 ),
214 # GPS status, lat, long
215 GPS_SCAN_STATUS: TrParam(
216 "Device.FAP.GPS.ScanStatus",
217 is_invasive=False,
218 type=TrParameterType.STRING,
219 is_optional=False,
220 ),
221 ParameterName.GPS_LAT: TrParam(
222 "Device.FAP.GPS.LockedLatitude",
223 is_invasive=False,
224 type=TrParameterType.STRING,
225 is_optional=False,
226 ),
227 ParameterName.GPS_LONG: TrParam(
228 "Device.FAP.GPS.LockedLongitude",
229 is_invasive=False,
230 type=TrParameterType.STRING,
231 is_optional=False,
232 ),
233 }
234
235 # Derived status params that don't have tr69 representation.
236 DERIVED_STATUS_PARAMETERS = {
237 ParameterName.RF_TX_STATUS: TrParam(
238 InvalidTrParamPath,
239 is_invasive=False,
240 type=TrParameterType.BOOLEAN,
241 is_optional=False,
242 ),
243 ParameterName.GPS_STATUS: TrParam(
244 InvalidTrParamPath,
245 is_invasive=False,
246 type=TrParameterType.BOOLEAN,
247 is_optional=False,
248 ),
249 ParameterName.PTP_STATUS: TrParam(
250 InvalidTrParamPath,
251 is_invasive=False,
252 type=TrParameterType.BOOLEAN,
253 is_optional=False,
254 ),
255 ParameterName.MME_STATUS: TrParam(
256 InvalidTrParamPath,
257 is_invasive=False,
258 type=TrParameterType.BOOLEAN,
259 is_optional=False,
260 ),
261 ParameterName.OP_STATE: TrParam(
262 InvalidTrParamPath,
263 is_invasive=False,
264 type=TrParameterType.BOOLEAN,
265 is_optional=False,
266 ),
267 }
268
269 @classmethod
270 def set_magma_device_cfg(
271 cls, name_to_val: Dict, device_cfg: EnodebConfiguration,
272 ):
273 """
274 Convert FreedomFiOne name_to_val representation to magma device_cfg
275 """
276 success_str = "SUCCESS" # String constant returned by radio
277 insync_str = "INSYNC"
278
279 if (
280 name_to_val.get(cls.DEFAULT_GW)
281 and name_to_val[cls.DEFAULT_GW].upper() != success_str
282 ):
283 # Nothing will proceed if the eNB doesn't have an IP on the WAN
284 serial_num = "unknown"
285 if device_cfg.has_parameter(ParameterName.SERIAL_NUMBER):
286 serial_num = device_cfg.get_parameter(ParameterName.SERIAL_NUMBER,)
287 EnodebdLogger.error(
288 "Radio with serial number %s doesn't have IP address " "on WAN",
289 serial_num,
290 )
291 device_cfg.set_parameter(
292 param_name=ParameterName.RF_TX_STATUS, value=False,
293 )
294 device_cfg.set_parameter(
295 param_name=ParameterName.GPS_STATUS, value=False,
296 )
297 device_cfg.set_parameter(
298 param_name=ParameterName.PTP_STATUS, value=False,
299 )
300 device_cfg.set_parameter(
301 param_name=ParameterName.MME_STATUS, value=False,
302 )
303 device_cfg.set_parameter(
304 param_name=ParameterName.OP_STATE, value=False,
305 )
306 return
307
308 if (
309 name_to_val.get(cls.SAS_STATUS)
310 and name_to_val[cls.SAS_STATUS].upper() == success_str
311 ):
312 device_cfg.set_parameter(
313 param_name=ParameterName.RF_TX_STATUS, value=True,
314 )
315 else:
316 # No sas grant so not transmitting. There is no explicit node for Tx
317 # in FreedomFiOne
318 device_cfg.set_parameter(
319 param_name=ParameterName.RF_TX_STATUS, value=False,
320 )
321
322 if (
323 name_to_val.get(cls.GPS_SCAN_STATUS)
324 and name_to_val[cls.GPS_SCAN_STATUS].upper() == success_str
325 ):
326 device_cfg.set_parameter(
327 param_name=ParameterName.GPS_STATUS, value=True,
328 )
329 # Time comes through GPS so can only be insync with GPS is
330 # in sync, we use PTP_STATUS field to overload timer is in Sync.
331 if (
332 name_to_val.get(cls.SYNC_STATUS)
333 and name_to_val[cls.SYNC_STATUS].upper() == insync_str
334 ):
335 device_cfg.set_parameter(
336 param_name=ParameterName.PTP_STATUS, value=True,
337 )
338 else:
339 device_cfg.set_parameter(
340 param_name=ParameterName.PTP_STATUS, value=False,
341 )
342 else:
343 device_cfg.set_parameter(
344 param_name=ParameterName.GPS_STATUS, value=False,
345 )
346 device_cfg.set_parameter(
347 param_name=ParameterName.PTP_STATUS, value=False,
348 )
349
350 if (
351 name_to_val.get(cls.DEFAULT_GW)
352 and name_to_val[cls.DEFAULT_GW].upper() == success_str
353 ):
354 device_cfg.set_parameter(
355 param_name=ParameterName.MME_STATUS, value=True,
356 )
357 else:
358 device_cfg.set_parameter(
359 param_name=ParameterName.MME_STATUS, value=False,
360 )
361
362 if (
363 name_to_val.get(cls.ENB_STATUS)
364 and name_to_val[cls.ENB_STATUS].upper() == success_str
365 ):
366 device_cfg.set_parameter(
367 param_name=ParameterName.OP_STATE, value=True,
368 )
369 else:
370 device_cfg.set_parameter(
371 param_name=ParameterName.OP_STATE, value=False,
372 )
373
374 pass_through_params = [ParameterName.GPS_LAT, ParameterName.GPS_LONG]
375 for name in pass_through_params:
376 device_cfg.set_parameter(name, name_to_val[name])
377
378
379class FreedomFiOneMiscParameters:
380 """
381 Default set of parameters that enable carrier aggregation and other
382 miscellaneous properties
383 """
384
385 FAP_CONTROL = "Device.Services.FAPService.1.FAPControl."
386 FAPSERVICE_PATH = "Device.Services.FAPService.1."
387
388 # Tunnel ref format clobber it to non IPSEC as we don't support
389 # IPSEC
390 TUNNEL_REF = "tunnel_ref"
391 PRIM_SOURCE = "prim_src"
392
393 # Carrier aggregation
394 CARRIER_AGG_ENABLE = "carrier_agg_enable"
395 CARRIER_NUMBER = "carrier_number" # Carrier aggregation params
396 CONTIGUOUS_CC = "contiguous_cc"
397 WEB_UI_ENABLE = "web_ui_enable" # Enable or disable local enb UI
398
399 MISC_PARAMETERS = {
400 WEB_UI_ENABLE: TrParam(
401 "Device.X_000E8F_DeviceFeature.X_000E8F_WebServerEnable",
402 is_invasive=False,
403 type=TrParameterType.BOOLEAN,
404 is_optional=False,
405 ),
406 CARRIER_AGG_ENABLE: TrParam(
407 FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CA_Enable",
408 is_invasive=False,
409 type=TrParameterType.BOOLEAN,
410 is_optional=False,
411 ),
412 CARRIER_NUMBER: TrParam(
413 FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_Cell_Number",
414 is_invasive=False,
415 type=TrParameterType.INT,
416 is_optional=False,
417 ),
418 CONTIGUOUS_CC: TrParam(
419 FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CELL_Freq_Contiguous",
420 is_invasive=False,
421 type=TrParameterType.INT,
422 is_optional=False,
423 ),
424 TUNNEL_REF: TrParam(
425 FAPSERVICE_PATH + "CellConfig.LTE.Tunnel.1.TunnelRef",
426 is_invasive=False,
427 type=TrParameterType.STRING,
428 is_optional=False,
429 ),
430 PRIM_SOURCE: TrParam(
431 FAPSERVICE_PATH + "REM.X_000E8F_tfcsManagerConfig.primSrc",
432 is_invasive=False,
433 type=TrParameterType.STRING,
434 is_optional=False,
435 ),
436 }
437
438 # Hardcoded defaults
439 defaults = {
440 # Use IPV4 only
441 TUNNEL_REF: "Device.IP.Interface.1.IPv4Address.1.",
442 # Only synchronize with GPS
443 PRIM_SOURCE: "GNSS",
444 # Always enable carrier aggregation for the CBRS bands
445 CARRIER_AGG_ENABLE: True,
446 CARRIER_NUMBER: 2, # CBRS has two carriers
447 CONTIGUOUS_CC: 0, # Its not contiguous carrier
448 WEB_UI_ENABLE: False, # Disable WebUI by default
449 }
450
451
452class FreedomFiOneHandler(BasicEnodebAcsStateMachine):
453 def __init__(self, service: MagmaService,) -> None:
454 self._state_map = {}
455 super().__init__(service=service, use_param_key=True)
456
457 def reboot_asap(self) -> None:
458 self.transition("reboot")
459
460 def is_enodeb_connected(self) -> bool:
461 return not isinstance(self.state, WaitInformState)
462
463 def _init_state_map(self) -> None:
464 self._state_map = {
465 # Inform comes in -> Respond with InformResponse
466 "wait_inform": WaitInformState(self, when_done="get_rpc_methods"),
467 # If first inform after boot -> GetRpc request comes in, if not
468 # empty request comes in => Transition to get_transient_params
469 "get_rpc_methods": FreedomFiOneGetInitState(
470 self, when_done="get_transient_params",
471 ),
472 # Read transient readonly params.
473 "get_transient_params": FreedomFiOneSendGetTransientParametersState(
474 self, when_done="get_params",
475 ),
476 "get_params": FreedomFiOneGetObjectParametersState(
477 self,
478 when_delete="delete_objs",
479 when_add="add_objs",
480 when_set="set_params",
481 when_skip="end_session",
482 ),
483 "delete_objs": DeleteObjectsState(
484 self, when_add="add_objs", when_skip="set_params",
485 ),
486 "add_objs": AddObjectsState(self, when_done="set_params"),
487 "set_params": SetParameterValuesState(self, when_done="wait_set_params",),
488 "wait_set_params": WaitSetParameterValuesState(
489 self,
490 when_done="check_get_params",
491 when_apply_invasive="check_get_params",
492 status_non_zero_allowed=True,
493 ),
494 "check_get_params": GetParametersState(
495 self, when_done="check_wait_get_params", request_all_params=True,
496 ),
497 "check_wait_get_params": WaitGetParametersState(
498 self, when_done="end_session",
499 ),
500 "end_session": EndSessionState(self),
501 # These states are only entered through manual user intervention
502 "reboot": EnbSendRebootState(self, when_done="wait_reboot"),
503 "wait_reboot": WaitRebootResponseState(
504 self, when_done="wait_post_reboot_inform",
505 ),
506 "wait_post_reboot_inform": WaitInformMRebootState(
507 self, when_done="wait_empty", when_timeout="wait_inform",
508 ),
509 # The states below are entered when an unexpected message type is
510 # received
511 "unexpected_fault": ErrorState(
512 self, inform_transition_target="wait_inform",
513 ),
514 }
515
516 @property
517 def device_name(self) -> str:
518 return EnodebDeviceName.FREEDOMFI_ONE
519
520 @property
521 def data_model_class(self) -> Type[DataModel]:
522 return FreedomFiOneTrDataModel
523
524 @property
525 def config_postprocessor(self) -> EnodebConfigurationPostProcessor:
526 return FreedomFiOneConfigurationInitializer(self)
527
528 @property
529 def state_map(self) -> Dict[str, EnodebAcsState]:
530 return self._state_map
531
532 @property
533 def disconnected_state_name(self) -> str:
534 return "wait_inform"
535
536 @property
537 def unexpected_fault_state_name(self) -> str:
538 return "unexpected_fault"
539
540
541class FreedomFiOneTrDataModel(DataModel):
542 """
543 Class to represent relevant data model parameters from TR-196/TR-098.
544 This class is effectively read-only.
545
546 These models have these idiosyncrasies (on account of running TR098):
547
548 - Parameter content root is different (InternetGatewayDevice)
549 - GetParameter queries with a wildcard e.g. InternetGatewayDevice. do
550 not respond with the full tree (we have to query all parameters)
551 - MME status is not exposed - we assume the MME is connected if
552 the eNodeB is transmitting (OpState=true)
553 - Parameters such as band capability/duplex config
554 are rooted under `boardconf.` and not the device config root
555 - Num PLMNs is not reported by these units
556 """
557
558 # Mapping of TR parameter paths to aliases
559 DEVICE_PATH = "Device."
560 FAPSERVICE_PATH = DEVICE_PATH + "Services.FAPService.1."
561 FAP_CONTROL = FAPSERVICE_PATH + "FAPControl."
562 BCCH = FAPSERVICE_PATH + "REM.LTE.Cell.1.BCCH."
563
564 PARAMETERS = {
565 # Top-level objects
566 ParameterName.DEVICE: TrParam(
567 DEVICE_PATH,
568 is_invasive=False,
569 type=TrParameterType.OBJECT,
570 is_optional=False,
571 ),
572 ParameterName.FAP_SERVICE: TrParam(
573 FAP_CONTROL,
574 is_invasive=False,
575 type=TrParameterType.OBJECT,
576 is_optional=False,
577 ),
578 # Device info
579 ParameterName.SW_VERSION: TrParam(
580 DEVICE_PATH + "DeviceInfo.SoftwareVersion",
581 is_invasive=False,
582 type=TrParameterType.STRING,
583 is_optional=False,
584 ),
585 ParameterName.SERIAL_NUMBER: TrParam(
586 DEVICE_PATH + "DeviceInfo.SerialNumber",
587 is_invasive=False,
588 type=TrParameterType.STRING,
589 is_optional=False,
590 ),
591 # RF-related parameters
592 ParameterName.EARFCNDL: TrParam(
593 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.EARFCNDL",
594 is_invasive=False,
595 type=TrParameterType.INT,
596 is_optional=False,
597 ),
598 ParameterName.DL_BANDWIDTH: TrParam(
599 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.DLBandwidth",
600 is_invasive=False,
601 type=TrParameterType.STRING,
602 is_optional=False,
603 ),
604 ParameterName.UL_BANDWIDTH: TrParam(
605 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.ULBandwidth",
606 is_invasive=False,
607 type=TrParameterType.STRING,
608 is_optional=False,
609 ),
610 ParameterName.PCI: TrParam(
611 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.PhyCellID",
612 is_invasive=False,
613 type=TrParameterType.STRING,
614 is_optional=False,
615 ),
616 ParameterName.SUBFRAME_ASSIGNMENT: TrParam(
617 FAPSERVICE_PATH + "CellConfig.LTE.RAN.PHY.TDDFrame.SubFrameAssignment",
618 is_invasive=False,
619 type=TrParameterType.BOOLEAN,
620 is_optional=False,
621 ),
622 ParameterName.SPECIAL_SUBFRAME_PATTERN: TrParam(
623 FAPSERVICE_PATH + "CellConfig.LTE.RAN.PHY.TDDFrame.SpecialSubframePatterns",
624 is_invasive=False,
625 type=TrParameterType.INT,
626 is_optional=False,
627 ),
628 ParameterName.CELL_ID: TrParam(
629 FAPSERVICE_PATH + "CellConfig.LTE.RAN.Common.CellIdentity",
630 is_invasive=False,
631 type=TrParameterType.UNSIGNED_INT,
632 is_optional=False,
633 ),
634 # Readonly LTE state
635 ParameterName.ADMIN_STATE: TrParam(
636 FAP_CONTROL + "LTE.AdminState",
637 is_invasive=False,
638 type=TrParameterType.BOOLEAN,
639 is_optional=False,
640 ),
641 ParameterName.GPS_ENABLE: TrParam(
642 DEVICE_PATH + "FAP.GPS.ScanOnBoot",
643 is_invasive=False,
644 type=TrParameterType.BOOLEAN,
645 is_optional=False,
646 ),
647 # Core network parameters
648 ParameterName.MME_IP: TrParam(
649 FAP_CONTROL + "LTE.Gateway.S1SigLinkServerList",
650 is_invasive=False,
651 type=TrParameterType.STRING,
652 is_optional=False,
653 ),
654 ParameterName.MME_PORT: TrParam(
655 FAP_CONTROL + "LTE.Gateway.S1SigLinkPort",
656 is_invasive=False,
657 type=TrParameterType.INT,
658 is_optional=False,
659 ),
660 ParameterName.NUM_PLMNS: TrParam(
661 FAPSERVICE_PATH + "CellConfig.LTE.EPC.PLMNListNumberOfEntries",
662 is_invasive=False,
663 type=TrParameterType.INT,
664 is_optional=False,
665 ),
666 ParameterName.TAC: TrParam(
667 FAPSERVICE_PATH + "CellConfig.LTE.EPC.TAC",
668 is_invasive=False,
669 type=TrParameterType.INT,
670 is_optional=False,
671 ),
672 # Management server parameters
673 ParameterName.PERIODIC_INFORM_ENABLE: TrParam(
674 DEVICE_PATH + "ManagementServer.PeriodicInformEnable",
675 is_invasive=False,
676 type=TrParameterType.BOOLEAN,
677 is_optional=False,
678 ),
679 ParameterName.PERIODIC_INFORM_INTERVAL: TrParam(
680 DEVICE_PATH + "ManagementServer.PeriodicInformInterval",
681 is_invasive=False,
682 type=TrParameterType.INT,
683 is_optional=False,
684 ),
685 # Performance management parameters
686 ParameterName.PERF_MGMT_ENABLE: TrParam(
687 DEVICE_PATH + "FAP.PerfMgmt.Config.1.Enable",
688 is_invasive=False,
689 type=TrParameterType.BOOLEAN,
690 is_optional=False,
691 ),
692 ParameterName.PERF_MGMT_UPLOAD_INTERVAL: TrParam(
693 DEVICE_PATH + "FAP.PerfMgmt.Config.1.PeriodicUploadInterval",
694 is_invasive=False,
695 type=TrParameterType.INT,
696 is_optional=False,
697 ),
698 ParameterName.PERF_MGMT_UPLOAD_URL: TrParam(
699 DEVICE_PATH + "FAP.PerfMgmt.Config.1.URL",
700 is_invasive=False,
701 type=TrParameterType.STRING,
702 is_optional=False,
703 ),
704 }
705 TRANSFORMS_FOR_ENB = {}
706 NUM_PLMNS_IN_CONFIG = 1
707 for i in range(1, NUM_PLMNS_IN_CONFIG + 1):
708 PARAMETERS[ParameterName.PLMN_N % i] = TrParam(
709 FAPSERVICE_PATH + "CellConfig.LTE.EPC.PLMNList.%d." % i,
710 is_invasive=False,
711 type=TrParameterType.STRING,
712 is_optional=False,
713 )
714 PARAMETERS[ParameterName.PLMN_N_CELL_RESERVED % i] = TrParam(
715 FAPSERVICE_PATH
716 + "CellConfig.LTE.EPC.PLMNList.%d.CellReservedForOperatorUse" % i,
717 is_invasive=False,
718 type=TrParameterType.BOOLEAN,
719 is_optional=False,
720 )
721 PARAMETERS[ParameterName.PLMN_N_ENABLE % i] = TrParam(
722 FAPSERVICE_PATH + "CellConfig.LTE.EPC.PLMNList.%d.Enable" % i,
723 is_invasive=False,
724 type=TrParameterType.BOOLEAN,
725 is_optional=False,
726 )
727 PARAMETERS[ParameterName.PLMN_N_PRIMARY % i] = TrParam(
728 FAPSERVICE_PATH + "CellConfig.LTE.EPC.PLMNList.%d.IsPrimary" % i,
729 is_invasive=False,
730 type=TrParameterType.BOOLEAN,
731 is_optional=False,
732 )
733 PARAMETERS[ParameterName.PLMN_N_PLMNID % i] = TrParam(
734 FAPSERVICE_PATH + "CellConfig.LTE.EPC.PLMNList.%d.PLMNID" % i,
735 is_invasive=False,
736 type=TrParameterType.STRING,
737 is_optional=False,
738 )
739
740 PARAMETERS.update(SASParameters.SAS_PARAMETERS)
741 PARAMETERS.update(FreedomFiOneMiscParameters.MISC_PARAMETERS)
742 PARAMETERS.update(StatusParameters.STATUS_PARAMETERS)
743 # These are stateful parameters that have no tr-69 representation
744 PARAMETERS.update(StatusParameters.DERIVED_STATUS_PARAMETERS)
745
746 TRANSFORMS_FOR_MAGMA = {
747 # We don't set these parameters
748 ParameterName.BAND_CAPABILITY: transform_for_magma.band_capability,
749 ParameterName.DUPLEX_MODE_CAPABILITY: transform_for_magma.duplex_mode,
750 }
751
752 @classmethod
753 def get_parameter(cls, param_name: ParameterName) -> Optional[TrParam]:
754 return cls.PARAMETERS.get(param_name)
755
756 @classmethod
757 def _get_magma_transforms(cls,) -> Dict[ParameterName, Callable[[Any], Any]]:
758 return cls.TRANSFORMS_FOR_MAGMA
759
760 @classmethod
761 def _get_enb_transforms(cls) -> Dict[ParameterName, Callable[[Any], Any]]:
762 return cls.TRANSFORMS_FOR_ENB
763
764 @classmethod
765 def get_load_parameters(cls) -> List[ParameterName]:
766 """
767 Load all the parameters instead of a subset.
768 """
769 return list(cls.PARAMETERS.keys())
770
771 @classmethod
772 def get_num_plmns(cls) -> int:
773 return cls.NUM_PLMNS_IN_CONFIG
774
775 @classmethod
776 def get_parameter_names(cls) -> List[ParameterName]:
777 excluded_params = [
778 str(ParameterName.DEVICE),
779 str(ParameterName.FAP_SERVICE),
780 ]
781 names = list(
782 filter(
783 lambda x: (not str(x).startswith("PLMN"))
784 and (str(x) not in excluded_params),
785 cls.PARAMETERS.keys(),
786 ),
787 )
788 return names
789
790 @classmethod
791 def get_numbered_param_names(cls,) -> Dict[ParameterName, List[ParameterName]]:
792 names = {}
793 for i in range(1, cls.NUM_PLMNS_IN_CONFIG + 1):
794 params = [
795 ParameterName.PLMN_N_CELL_RESERVED % i,
796 ParameterName.PLMN_N_ENABLE % i,
797 ParameterName.PLMN_N_PRIMARY % i,
798 ParameterName.PLMN_N_PLMNID % i,
799 ]
800 names[ParameterName.PLMN_N % i] = params
801
802 return names
803
804 @classmethod
805 def get_sas_param_names(cls) -> List[ParameterName]:
806 return SASParameters.SAS_PARAMETERS.keys()
807
808
809class FreedomFiOneConfigurationInitializer(EnodebConfigurationPostProcessor):
810 """
811 Class to add the sas related parameters to the desired config.
812 """
813
814 SAS_KEY = "sas"
815 WEB_UI_ENABLE_LIST_KEY = "web_ui_enable_list"
816
817 def __init__(self, acs: EnodebAcsStateMachine):
818 super().__init__()
819 self.acs = acs
820
821 def postprocess(
822 self, mconfig: Any, service_cfg: Any, desired_cfg: EnodebConfiguration,
823 ) -> None:
824 # TODO: Get this config from the domain proxy
825 # TODO @amarpad, set these when DProxy integration is done.
826 # For now the radio will directly talk to the SAS and get these
827 # attributes.
828 desired_cfg.delete_parameter(ParameterName.EARFCNDL)
829 desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH)
830 desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH)
831
832 # go through misc parameters and set them to default.
833 for name, val in FreedomFiOneMiscParameters.defaults.items():
834 desired_cfg.set_parameter(name, val)
835
836 # Bump up the parameter key version
837 self.acs.parameter_version_inc()
838
839 if self.WEB_UI_ENABLE_LIST_KEY in service_cfg:
840 serial_nos = service_cfg.get(self.WEB_UI_ENABLE_LIST_KEY)
841 if self.acs.device_cfg.has_parameter(ParameterName.SERIAL_NUMBER,):
842 if self.acs.get_parameter(ParameterName.SERIAL_NUMBER) in serial_nos:
843 desired_cfg.set_parameter(
844 FreedomFiOneMiscParameters.WEB_UI_ENABLE, True,
845 )
846 else:
847 # This should not happen
848 EnodebdLogger.error("Serial number unknown for device")
849
850 if self.SAS_KEY not in service_cfg:
851 return
852
853 sas_cfg = service_cfg[self.SAS_KEY]
854 sas_param_names = self.acs.data_model.get_sas_param_names()
855 for name, val in sas_cfg.items():
856 if name not in sas_param_names:
857 EnodebdLogger.warning("Ignoring attribute %s", name)
858 continue
859 desired_cfg.set_parameter(name, val)
860
861
862class FreedomFiOneSendGetTransientParametersState(EnodebAcsState):
863 """
864 Periodically read eNodeB status. Note: keep frequency low to avoid
865 backing up large numbers of read operations if enodebd is busy.
866 Some eNB parameters are read only and updated by the eNB itself.
867 """
868
869 def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
870 super().__init__()
871 self.acs = acs
872 self.done_transition = when_done
873
874 def get_msg(self, message: Any) -> AcsMsgAndTransition:
875 request = models.GetParameterValues()
876 request.ParameterNames = models.ParameterNames()
877 request.ParameterNames.string = []
878 for _, tr_param in StatusParameters.STATUS_PARAMETERS.items():
879 path = tr_param.path
880 request.ParameterNames.string.append(path)
881 request.ParameterNames.arrayType = "xsd:string[%d]" % len(
882 request.ParameterNames.string
883 )
884
885 return AcsMsgAndTransition(msg=request, next_state=None)
886
887 def read_msg(self, message: Any) -> AcsReadMsgResult:
888
889 if not isinstance(message, models.GetParameterValuesResponse):
890 return AcsReadMsgResult(msg_handled=False, next_state=None)
891 # Current values of the fetched parameters
892 name_to_val = parse_get_parameter_values_response(self.acs.data_model, message,)
893 EnodebdLogger.debug("Received Parameters: %s", str(name_to_val))
894
895 # Update device configuration
896 StatusParameters.set_magma_device_cfg(
897 name_to_val, self.acs.device_cfg,
898 )
899
900 return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition,)
901
902 def state_description(self) -> str:
903 return "Getting transient read-only parameters"
904
905
906class FreedomFiOneGetInitState(EnodebAcsState):
907 """
908 After the first Inform message the following can happen:
909 1 - eNB can try to learn the RPC method of the ACS, reply back with the
910 RPC response (happens right after boot)
911 2 - eNB can send an empty message -> This means that the eNB is already
912 provisioned so transition to next state. Only transition to next state
913 after this message.
914 3 - Some other method call that we don't care about so ignore.
915 expected that the eNB -> This is an unhandled state so unlikely
916 """
917
918 def __init__(self, acs: EnodebAcsStateMachine, when_done):
919 super().__init__()
920 self.acs = acs
921 self.done_transition = when_done
922 self._is_rpc_request = False
923
924 def get_msg(self, message: Any) -> AcsMsgAndTransition:
925 """
926 Return empty message response if care about this
927 message type otherwise return empty RPC methods response.
928 """
929 if not self._is_rpc_request:
930 resp = models.DummyInput()
931 return AcsMsgAndTransition(msg=resp, next_state=None)
932
933 resp = models.GetRPCMethodsResponse()
934 resp.MethodList = models.MethodList()
935 RPC_METHODS = ["Inform", "GetRPCMethods", "TransferComplete"]
936 resp.MethodList.arrayType = "xsd:string[%d]" % len(RPC_METHODS)
937 resp.MethodList.string = RPC_METHODS
938 # Don't transition to next state wait for the empty HTTP post
939 return AcsMsgAndTransition(msg=resp, next_state=None)
940
941 def read_msg(self, message: Any) -> AcsReadMsgResult:
942 # If this is a regular Inform, not after a reboot we'll get an empty
943 # message, in this case transition to the next state. We consider
944 # this phase as "initialized"
945 if isinstance(message, models.DummyInput):
946 return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition,)
947 if not isinstance(message, models.GetRPCMethods):
948 # Unexpected, just don't die, ignore message.
949 logging.error("Ignoring message %s", str(type(message)))
950 # Set this so get_msg will return an empty message
951 self._is_rpc_request = False
952 else:
953 # Return a valid RPC response
954 self._is_rpc_request = True
955 return AcsReadMsgResult(msg_handled=True, next_state=None)
956
957 def state_description(self) -> str:
958 return "Initializing the post boot sequence for eNB"
959
960
961class FreedomFiOneGetObjectParametersState(EnodebAcsState):
962 """
963 Get information on parameters belonging to objects that can be added or
964 removed from the configuration.
965
966 Englewood will report a parameter value as None if it does not exist
967 in the data model, rather than replying with a Fault message like most
968 eNB devices.
969 """
970
971 def __init__(
972 self,
973 acs: EnodebAcsStateMachine,
974 when_delete: str,
975 when_add: str,
976 when_set: str,
977 when_skip: str,
978 ):
979 super().__init__()
980 self.acs = acs
981 self.rm_obj_transition = when_delete
982 self.add_obj_transition = when_add
983 self.set_params_transition = when_set
984 self.skip_transition = when_skip
985
986 def get_params_to_get(self, data_model: DataModel,) -> List[ParameterName]:
987 names = []
988
989 # First get base params
990 names = get_params_to_get(
991 self.acs.device_cfg, self.acs.data_model, request_all_params=True,
992 )
993 # Add object params.
994 num_plmns = data_model.get_num_plmns()
995 obj_to_params = data_model.get_numbered_param_names()
996 for i in range(1, num_plmns + 1):
997 obj_name = ParameterName.PLMN_N % i
998 desired = obj_to_params[obj_name]
999 names += desired
1000 return names
1001
1002 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1003 """ Respond with GetParameterValuesRequest """
1004 names = self.get_params_to_get(self.acs.data_model,)
1005
1006 # Generate the request
1007 request = models.GetParameterValues()
1008 request.ParameterNames = models.ParameterNames()
1009 request.ParameterNames.arrayType = "xsd:string[%d]" % len(names)
1010 request.ParameterNames.string = []
1011 for name in names:
1012 path = self.acs.data_model.get_parameter(name).path
1013 if path is not InvalidTrParamPath:
1014 # Only get data elements backed by tr69 path
1015 request.ParameterNames.string.append(path)
1016
1017 return AcsMsgAndTransition(msg=request, next_state=None)
1018
1019 def read_msg(self, message: Any) -> AcsReadMsgResult:
1020 """
1021 Process GetParameterValuesResponse
1022 """
1023 if not isinstance(message, models.GetParameterValuesResponse):
1024 return AcsReadMsgResult(msg_handled=False, next_state=None)
1025
1026 path_to_val = {}
1027 for param_value_struct in message.ParameterList.ParameterValueStruct:
1028 path_to_val[param_value_struct.Name] = param_value_struct.Value.Data
1029
1030 EnodebdLogger.debug("Received object parameters: %s", str(path_to_val))
1031
1032 # Parse simple params
1033 param_name_list = self.acs.data_model.get_parameter_names()
1034 for name in param_name_list:
1035 path = self.acs.data_model.get_parameter(name).path
1036 if path in path_to_val:
1037 value = path_to_val.get(path)
1038 magma_val = self.acs.data_model.transform_for_magma(name, value,)
1039 self.acs.device_cfg.set_parameter(name, magma_val)
1040
1041 # Parse object params
1042 num_plmns = self.acs.data_model.get_num_plmns()
1043 for i in range(1, num_plmns + 1):
1044 obj_name = ParameterName.PLMN_N % i
1045 obj_to_params = self.acs.data_model.get_numbered_param_names()
1046 param_name_list = obj_to_params[obj_name]
1047 for name in param_name_list:
1048 path = self.acs.data_model.get_parameter(name).path
1049 if path in path_to_val:
1050 value = path_to_val.get(path)
1051 if value is None:
1052 continue
1053 if obj_name not in self.acs.device_cfg.get_object_names():
1054 self.acs.device_cfg.add_object(obj_name)
1055 magma_value = self.acs.data_model.transform_for_magma(name, value)
1056 self.acs.device_cfg.set_parameter_for_object(
1057 name, magma_value, obj_name,
1058 )
1059 # Now we have enough information to build the desired configuration
1060 if self.acs.desired_cfg is None:
1061 self.acs.desired_cfg = build_desired_config(
1062 self.acs.mconfig,
1063 self.acs.service_config,
1064 self.acs.device_cfg,
1065 self.acs.data_model,
1066 self.acs.config_postprocessor,
1067 )
1068
1069 if (
1070 len(get_all_objects_to_delete(self.acs.desired_cfg, self.acs.device_cfg,),)
1071 > 0
1072 ):
1073 return AcsReadMsgResult(
1074 msg_handled=True, next_state=self.rm_obj_transition,
1075 )
1076 elif (
1077 len(get_all_objects_to_add(self.acs.desired_cfg, self.acs.device_cfg,),) > 0
1078 ):
1079 return AcsReadMsgResult(
1080 msg_handled=True, next_state=self.add_obj_transition,
1081 )
1082 elif (
1083 len(
1084 get_all_param_values_to_set(
1085 self.acs.desired_cfg, self.acs.device_cfg, self.acs.data_model,
1086 ),
1087 )
1088 > 0
1089 ):
1090 return AcsReadMsgResult(
1091 msg_handled=True, next_state=self.set_params_transition,
1092 )
1093 return AcsReadMsgResult(msg_handled=True, next_state=self.skip_transition,)
1094
1095 def state_description(self) -> str:
1096 return "Getting well known parameters"