blob: bfa66eb0af713b43353886bfbf2422b6da34661a [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)
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +080027from configuration.service_configs import load_enb_config
Wei-Yu Chen49950b92021-11-08 19:19:18 +080028from device_config.configuration_init import build_desired_config
29from device_config.enodeb_config_postprocessor import EnodebConfigurationPostProcessor
30from device_config.enodeb_configuration import EnodebConfiguration
31from devices.device_utils import EnodebDeviceName
32from logger import EnodebdLogger
33from state_machines.acs_state_utils import (
34 get_all_objects_to_add,
35 get_all_objects_to_delete,
36 get_all_param_values_to_set,
37 get_params_to_get,
38 parse_get_parameter_values_response,
39)
40from state_machines.enb_acs import EnodebAcsStateMachine
41from state_machines.enb_acs_impl import BasicEnodebAcsStateMachine
42from state_machines.enb_acs_states import (
43 AcsMsgAndTransition,
44 AcsReadMsgResult,
45 AddObjectsState,
46 DeleteObjectsState,
47 EnbSendRebootState,
48 EndSessionState,
49 EnodebAcsState,
50 ErrorState,
51 GetParametersState,
52 SetParameterValuesState,
53 WaitGetParametersState,
54 WaitInformMRebootState,
55 WaitInformState,
56 WaitRebootResponseState,
57 WaitSetParameterValuesState,
58)
59from tr069 import models
60
61
62class SASParameters:
63 """ Class modeling the SAS parameters and their TR path"""
64
65 # SAS parameters for FreedomFiOne
66 FAP_CONTROL = "Device.Services.FAPService.1.FAPControl."
67 FAPSERVICE_PATH = "Device.Services.FAPService.1."
68
69 # Sas management parameters
70 SAS_ENABLE = "sas_enabled"
71 SAS_SERVER_URL = "sas_server_url"
72 SAS_UID = "sas_uid"
73 SAS_CATEGORY = "sas_category"
74 SAS_CHANNEL_TYPE = "sas_channel_type"
75 SAS_CERT_SUBJECT = "sas_cert_subject"
76 SAS_IC_GROUP_ID = "sas_icg_group_id"
77 SAS_LOCATION = "sas_location"
78 SAS_HEIGHT_TYPE = "sas_height_type"
79 SAS_CPI_ENABLE = "sas_cpi_enable"
80 SAS_CPI_IPE = "sas_cpi_ipe" # Install param supplied enable
81 FREQ_BAND_1 = "freq_band_1"
82 FREQ_BAND_2 = "freq_band_2"
83 # For CBRS radios we set this to the limit and the SAS can reduce the
84 # power if needed.
85 TX_POWER_CONFIG = "tx_power_config"
86
87 SAS_PARAMETERS = {
88 SAS_ENABLE: TrParam(
89 FAP_CONTROL + "LTE.X_000E8F_SAS.Enable",
90 is_invasive=False,
91 type=TrParameterType.BOOLEAN,
92 is_optional=False,
93 ),
94 SAS_SERVER_URL: TrParam(
95 FAP_CONTROL + "LTE.X_000E8F_SAS.Server",
96 is_invasive=False,
97 type=TrParameterType.STRING,
98 is_optional=False,
99 ),
100 SAS_UID: TrParam(
101 FAP_CONTROL + "LTE.X_000E8F_SAS.UserContactInformation",
102 is_invasive=False,
103 type=TrParameterType.STRING,
104 is_optional=False,
105 ),
106 SAS_CATEGORY: TrParam(
107 FAP_CONTROL + "LTE.X_000E8F_SAS.Category",
108 is_invasive=False,
109 type=TrParameterType.STRING,
110 is_optional=False,
111 ),
112 SAS_CHANNEL_TYPE: TrParam(
113 FAP_CONTROL + "LTE.X_000E8F_SAS.ProtectionLevel",
114 is_invasive=False,
115 type=TrParameterType.STRING,
116 is_optional=False,
117 ),
118 SAS_CERT_SUBJECT: TrParam(
119 FAP_CONTROL + "LTE.X_000E8F_SAS.CertSubject",
120 is_invasive=False,
121 type=TrParameterType.STRING,
122 is_optional=False,
123 ),
124 # SAS_IC_GROUP_ID: TrParam(
125 # FAP_CONTROL + 'LTE.X_000E8F_SAS.ICGGroupId', is_invasive=False,
126 # type=TrParameterType.STRING, False),
127 SAS_LOCATION: TrParam(
128 FAP_CONTROL + "LTE.X_000E8F_SAS.Location",
129 is_invasive=False,
130 type=TrParameterType.STRING,
131 is_optional=False,
132 ),
133 SAS_HEIGHT_TYPE: TrParam(
134 FAP_CONTROL + "LTE.X_000E8F_SAS.HeightType",
135 is_invasive=False,
136 type=TrParameterType.STRING,
137 is_optional=False,
138 ),
139 SAS_CPI_ENABLE: TrParam(
140 FAP_CONTROL + "LTE.X_000E8F_SAS.CPIEnable",
141 is_invasive=False,
142 type=TrParameterType.BOOLEAN,
143 is_optional=False,
144 ),
145 SAS_CPI_IPE: TrParam(
146 FAP_CONTROL + "LTE.X_000E8F_SAS.CPIInstallParamSuppliedEnable",
147 is_invasive=False,
148 type=TrParameterType.BOOLEAN,
149 is_optional=False,
150 ),
151 FREQ_BAND_1: TrParam(
152 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.FreqBandIndicator",
153 is_invasive=False,
154 type=TrParameterType.UNSIGNED_INT,
155 is_optional=False,
156 ),
157 FREQ_BAND_2: TrParam(
158 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_000E8F_FreqBandIndicator2",
159 is_invasive=False,
160 type=TrParameterType.UNSIGNED_INT,
161 is_optional=False,
162 ),
163 TX_POWER_CONFIG: TrParam(
164 FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_000E8F_TxPowerConfig",
165 is_invasive=True,
166 type=TrParameterType.INT,
167 is_optional=False,
168 ),
169 }
170
171
172class StatusParameters:
173 """
174 Stateful class that converts eNB status to Magma understood status.
175 FreedomFiOne has many status params that interact with each other.
176 This class maintains the business logic of parsing these status params
177 and converting it to Magma understood fields.
178 """
179
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800180 DEFGW_STATUS_PATH = "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_DEFGW_Status"
181 SAS_STATUS_PATH = "Device.Services.FAPService.1.FAPControl.LTE.X_SCM_SAS.State"
182 ENB_STATUS_PATH = "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_eNB_Status"
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800183
184 # Status parameters
185 DEFAULT_GW = "defaultGW"
186 SYNC_STATUS = "syncStatus"
187 SAS_STATUS = "sasStatus"
188 ENB_STATUS = "enbStatus"
189 GPS_SCAN_STATUS = "gpsScanStatus"
190
191 STATUS_PARAMETERS = {
192 # Status nodes
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800193 # This works
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800194 DEFAULT_GW: TrParam(
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800195 DEFGW_STATUS_PATH,
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800196 is_invasive=False,
197 type=TrParameterType.STRING,
198 is_optional=False,
199 ),
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800200 # SYNC_STATUS: TrParam(
201 # STATUS_PATH + 'X_000E8F_Sync_Status', is_invasive=False,
202 # type=TrParameterType.STRING, is_optional=False,
203 # ),
204 # This works
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800205 SAS_STATUS: TrParam(
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800206 SAS_STATUS_PATH,
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800207 is_invasive=False,
208 type=TrParameterType.STRING,
209 is_optional=False,
210 ),
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800211 # This doesn't work
212 # ENB_STATUS: TrParam(
213 # ENB_STATUS_PATH, is_invasive=False,
214 # type=TrParameterType.STRING, is_optional=False,
215 # ),
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800216 # GPS status, lat, long
217 GPS_SCAN_STATUS: TrParam(
218 "Device.FAP.GPS.ScanStatus",
219 is_invasive=False,
220 type=TrParameterType.STRING,
221 is_optional=False,
222 ),
223 ParameterName.GPS_LAT: TrParam(
224 "Device.FAP.GPS.LockedLatitude",
225 is_invasive=False,
226 type=TrParameterType.STRING,
227 is_optional=False,
228 ),
229 ParameterName.GPS_LONG: TrParam(
230 "Device.FAP.GPS.LockedLongitude",
231 is_invasive=False,
232 type=TrParameterType.STRING,
233 is_optional=False,
234 ),
235 }
236
237 # Derived status params that don't have tr69 representation.
238 DERIVED_STATUS_PARAMETERS = {
239 ParameterName.RF_TX_STATUS: TrParam(
240 InvalidTrParamPath,
241 is_invasive=False,
242 type=TrParameterType.BOOLEAN,
243 is_optional=False,
244 ),
245 ParameterName.GPS_STATUS: TrParam(
246 InvalidTrParamPath,
247 is_invasive=False,
248 type=TrParameterType.BOOLEAN,
249 is_optional=False,
250 ),
251 ParameterName.PTP_STATUS: TrParam(
252 InvalidTrParamPath,
253 is_invasive=False,
254 type=TrParameterType.BOOLEAN,
255 is_optional=False,
256 ),
257 ParameterName.MME_STATUS: TrParam(
258 InvalidTrParamPath,
259 is_invasive=False,
260 type=TrParameterType.BOOLEAN,
261 is_optional=False,
262 ),
263 ParameterName.OP_STATE: TrParam(
264 InvalidTrParamPath,
265 is_invasive=False,
266 type=TrParameterType.BOOLEAN,
267 is_optional=False,
268 ),
269 }
270
271 @classmethod
272 def set_magma_device_cfg(
273 cls, name_to_val: Dict, device_cfg: EnodebConfiguration,
274 ):
275 """
276 Convert FreedomFiOne name_to_val representation to magma device_cfg
277 """
278 success_str = "SUCCESS" # String constant returned by radio
279 insync_str = "INSYNC"
280
281 if (
282 name_to_val.get(cls.DEFAULT_GW)
283 and name_to_val[cls.DEFAULT_GW].upper() != success_str
284 ):
285 # Nothing will proceed if the eNB doesn't have an IP on the WAN
286 serial_num = "unknown"
287 if device_cfg.has_parameter(ParameterName.SERIAL_NUMBER):
288 serial_num = device_cfg.get_parameter(ParameterName.SERIAL_NUMBER,)
289 EnodebdLogger.error(
290 "Radio with serial number %s doesn't have IP address " "on WAN",
291 serial_num,
292 )
293 device_cfg.set_parameter(
294 param_name=ParameterName.RF_TX_STATUS, value=False,
295 )
296 device_cfg.set_parameter(
297 param_name=ParameterName.GPS_STATUS, value=False,
298 )
299 device_cfg.set_parameter(
300 param_name=ParameterName.PTP_STATUS, value=False,
301 )
302 device_cfg.set_parameter(
303 param_name=ParameterName.MME_STATUS, value=False,
304 )
305 device_cfg.set_parameter(
306 param_name=ParameterName.OP_STATE, value=False,
307 )
308 return
309
310 if (
311 name_to_val.get(cls.SAS_STATUS)
312 and name_to_val[cls.SAS_STATUS].upper() == success_str
313 ):
314 device_cfg.set_parameter(
315 param_name=ParameterName.RF_TX_STATUS, value=True,
316 )
317 else:
318 # No sas grant so not transmitting. There is no explicit node for Tx
319 # in FreedomFiOne
320 device_cfg.set_parameter(
321 param_name=ParameterName.RF_TX_STATUS, value=False,
322 )
323
324 if (
325 name_to_val.get(cls.GPS_SCAN_STATUS)
326 and name_to_val[cls.GPS_SCAN_STATUS].upper() == success_str
327 ):
328 device_cfg.set_parameter(
329 param_name=ParameterName.GPS_STATUS, value=True,
330 )
331 # Time comes through GPS so can only be insync with GPS is
332 # in sync, we use PTP_STATUS field to overload timer is in Sync.
333 if (
334 name_to_val.get(cls.SYNC_STATUS)
335 and name_to_val[cls.SYNC_STATUS].upper() == insync_str
336 ):
337 device_cfg.set_parameter(
338 param_name=ParameterName.PTP_STATUS, value=True,
339 )
340 else:
341 device_cfg.set_parameter(
342 param_name=ParameterName.PTP_STATUS, value=False,
343 )
344 else:
345 device_cfg.set_parameter(
346 param_name=ParameterName.GPS_STATUS, value=False,
347 )
348 device_cfg.set_parameter(
349 param_name=ParameterName.PTP_STATUS, value=False,
350 )
351
352 if (
353 name_to_val.get(cls.DEFAULT_GW)
354 and name_to_val[cls.DEFAULT_GW].upper() == success_str
355 ):
356 device_cfg.set_parameter(
357 param_name=ParameterName.MME_STATUS, value=True,
358 )
359 else:
360 device_cfg.set_parameter(
361 param_name=ParameterName.MME_STATUS, value=False,
362 )
363
364 if (
365 name_to_val.get(cls.ENB_STATUS)
366 and name_to_val[cls.ENB_STATUS].upper() == success_str
367 ):
368 device_cfg.set_parameter(
369 param_name=ParameterName.OP_STATE, value=True,
370 )
371 else:
372 device_cfg.set_parameter(
373 param_name=ParameterName.OP_STATE, value=False,
374 )
375
376 pass_through_params = [ParameterName.GPS_LAT, ParameterName.GPS_LONG]
377 for name in pass_through_params:
378 device_cfg.set_parameter(name, name_to_val[name])
379
380
381class FreedomFiOneMiscParameters:
382 """
383 Default set of parameters that enable carrier aggregation and other
384 miscellaneous properties
385 """
386
387 FAP_CONTROL = "Device.Services.FAPService.1.FAPControl."
388 FAPSERVICE_PATH = "Device.Services.FAPService.1."
389
390 # Tunnel ref format clobber it to non IPSEC as we don't support
391 # IPSEC
392 TUNNEL_REF = "tunnel_ref"
393 PRIM_SOURCE = "prim_src"
394
395 # Carrier aggregation
396 CARRIER_AGG_ENABLE = "carrier_agg_enable"
397 CARRIER_NUMBER = "carrier_number" # Carrier aggregation params
398 CONTIGUOUS_CC = "contiguous_cc"
399 WEB_UI_ENABLE = "web_ui_enable" # Enable or disable local enb UI
400
401 MISC_PARAMETERS = {
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800402 # WEB_UI_ENABLE: TrParam(
403 # 'Device.X_000E8F_DeviceFeature.X_000E8F_WebServerEnable',
404 # is_invasive=False,
405 # type=TrParameterType.BOOLEAN, is_optional=False,
406 # ),
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800407 CARRIER_AGG_ENABLE: TrParam(
408 FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CA_Enable",
409 is_invasive=False,
410 type=TrParameterType.BOOLEAN,
411 is_optional=False,
412 ),
413 CARRIER_NUMBER: TrParam(
414 FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_Cell_Number",
415 is_invasive=False,
416 type=TrParameterType.INT,
417 is_optional=False,
418 ),
419 CONTIGUOUS_CC: TrParam(
420 FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CELL_Freq_Contiguous",
421 is_invasive=False,
422 type=TrParameterType.INT,
423 is_optional=False,
424 ),
425 TUNNEL_REF: TrParam(
426 FAPSERVICE_PATH + "CellConfig.LTE.Tunnel.1.TunnelRef",
427 is_invasive=False,
428 type=TrParameterType.STRING,
429 is_optional=False,
430 ),
431 PRIM_SOURCE: TrParam(
432 FAPSERVICE_PATH + "REM.X_000E8F_tfcsManagerConfig.primSrc",
433 is_invasive=False,
434 type=TrParameterType.STRING,
435 is_optional=False,
436 ),
437 }
438
439 # Hardcoded defaults
440 defaults = {
441 # Use IPV4 only
442 TUNNEL_REF: "Device.IP.Interface.1.IPv4Address.1.",
443 # Only synchronize with GPS
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800444 PRIM_SOURCE: "FREE_RUNNING",
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800445 # Always enable carrier aggregation for the CBRS bands
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800446 CARRIER_AGG_ENABLE: False,
447 CARRIER_NUMBER: 1, # CBRS has two carriers
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800448 CONTIGUOUS_CC: 0, # Its not contiguous carrier
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800449 }
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 ),
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800660 # It may not work, comment out first
661 # ParameterName.NUM_PLMNS: TrParam(
662 # FAPSERVICE_PATH + 'CellConfig.LTE.EPC.PLMNListNumberOfEntries',
663 # is_invasive=False,
664 # type=TrParameterType.INT, is_optional=False,
665 # ),
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800666 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:
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800824
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800825 desired_cfg.delete_parameter(ParameterName.EARFCNDL)
826 desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH)
827 desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH)
828
829 # go through misc parameters and set them to default.
830 for name, val in FreedomFiOneMiscParameters.defaults.items():
831 desired_cfg.set_parameter(name, val)
832
833 # Bump up the parameter key version
834 self.acs.parameter_version_inc()
835
836 if self.WEB_UI_ENABLE_LIST_KEY in service_cfg:
837 serial_nos = service_cfg.get(self.WEB_UI_ENABLE_LIST_KEY)
838 if self.acs.device_cfg.has_parameter(ParameterName.SERIAL_NUMBER,):
839 if self.acs.get_parameter(ParameterName.SERIAL_NUMBER) in serial_nos:
840 desired_cfg.set_parameter(
841 FreedomFiOneMiscParameters.WEB_UI_ENABLE, True,
842 )
843 else:
844 # This should not happen
845 EnodebdLogger.error("Serial number unknown for device")
846
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800847 # Load eNB customized configuration from "./magma_config/serial_number/"
848 # and configure each connected eNB based on serial number
849 enbcfg = load_enb_config()
850 sn = self.acs.get_parameter(ParameterName.SERIAL_NUMBER)
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800851
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800852 for name, val in enbcfg.get(sn, {}).items():
853 # The SAS configuration for eNodeB
854 if name in ["sas", "cell"]:
855 for subname, subval in val.items():
856 print("Config %s updated to: %s" % (subname, subval))
857 desired_cfg.set_parameter(subname, subval)
858
859 print(desired_cfg)
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800860
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:
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800875
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800876 request = models.GetParameterValues()
877 request.ParameterNames = models.ParameterNames()
878 request.ParameterNames.string = []
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800879
880 # request = models.GetParameterNames()
881 # request.ParameterPath = "Device."
882 # request.NextLevel = False
883
884 # Get the status parameters which was defined in Line 171
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800885 for _, tr_param in StatusParameters.STATUS_PARAMETERS.items():
886 path = tr_param.path
887 request.ParameterNames.string.append(path)
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800888
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800889 request.ParameterNames.arrayType = "xsd:string[%d]" % len(
890 request.ParameterNames.string
891 )
892
893 return AcsMsgAndTransition(msg=request, next_state=None)
894
895 def read_msg(self, message: Any) -> AcsReadMsgResult:
896
897 if not isinstance(message, models.GetParameterValuesResponse):
898 return AcsReadMsgResult(msg_handled=False, next_state=None)
899 # Current values of the fetched parameters
900 name_to_val = parse_get_parameter_values_response(self.acs.data_model, message,)
901 EnodebdLogger.debug("Received Parameters: %s", str(name_to_val))
902
903 # Update device configuration
904 StatusParameters.set_magma_device_cfg(
905 name_to_val, self.acs.device_cfg,
906 )
907
908 return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition,)
909
910 def state_description(self) -> str:
911 return "Getting transient read-only parameters"
912
913
914class FreedomFiOneGetInitState(EnodebAcsState):
915 """
916 After the first Inform message the following can happen:
917 1 - eNB can try to learn the RPC method of the ACS, reply back with the
918 RPC response (happens right after boot)
919 2 - eNB can send an empty message -> This means that the eNB is already
920 provisioned so transition to next state. Only transition to next state
921 after this message.
922 3 - Some other method call that we don't care about so ignore.
923 expected that the eNB -> This is an unhandled state so unlikely
924 """
925
926 def __init__(self, acs: EnodebAcsStateMachine, when_done):
927 super().__init__()
928 self.acs = acs
929 self.done_transition = when_done
930 self._is_rpc_request = False
931
932 def get_msg(self, message: Any) -> AcsMsgAndTransition:
933 """
934 Return empty message response if care about this
935 message type otherwise return empty RPC methods response.
936 """
937 if not self._is_rpc_request:
938 resp = models.DummyInput()
939 return AcsMsgAndTransition(msg=resp, next_state=None)
940
941 resp = models.GetRPCMethodsResponse()
942 resp.MethodList = models.MethodList()
943 RPC_METHODS = ["Inform", "GetRPCMethods", "TransferComplete"]
944 resp.MethodList.arrayType = "xsd:string[%d]" % len(RPC_METHODS)
945 resp.MethodList.string = RPC_METHODS
946 # Don't transition to next state wait for the empty HTTP post
947 return AcsMsgAndTransition(msg=resp, next_state=None)
948
949 def read_msg(self, message: Any) -> AcsReadMsgResult:
950 # If this is a regular Inform, not after a reboot we'll get an empty
951 # message, in this case transition to the next state. We consider
952 # this phase as "initialized"
953 if isinstance(message, models.DummyInput):
954 return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition,)
955 if not isinstance(message, models.GetRPCMethods):
956 # Unexpected, just don't die, ignore message.
957 logging.error("Ignoring message %s", str(type(message)))
958 # Set this so get_msg will return an empty message
959 self._is_rpc_request = False
960 else:
961 # Return a valid RPC response
962 self._is_rpc_request = True
963 return AcsReadMsgResult(msg_handled=True, next_state=None)
964
965 def state_description(self) -> str:
966 return "Initializing the post boot sequence for eNB"
967
968
969class FreedomFiOneGetObjectParametersState(EnodebAcsState):
970 """
971 Get information on parameters belonging to objects that can be added or
972 removed from the configuration.
973
974 Englewood will report a parameter value as None if it does not exist
975 in the data model, rather than replying with a Fault message like most
976 eNB devices.
977 """
978
979 def __init__(
980 self,
981 acs: EnodebAcsStateMachine,
982 when_delete: str,
983 when_add: str,
984 when_set: str,
985 when_skip: str,
986 ):
987 super().__init__()
988 self.acs = acs
989 self.rm_obj_transition = when_delete
990 self.add_obj_transition = when_add
991 self.set_params_transition = when_set
992 self.skip_transition = when_skip
993
994 def get_params_to_get(self, data_model: DataModel,) -> List[ParameterName]:
995 names = []
996
997 # First get base params
998 names = get_params_to_get(
999 self.acs.device_cfg, self.acs.data_model, request_all_params=True,
1000 )
1001 # Add object params.
1002 num_plmns = data_model.get_num_plmns()
1003 obj_to_params = data_model.get_numbered_param_names()
1004 for i in range(1, num_plmns + 1):
1005 obj_name = ParameterName.PLMN_N % i
1006 desired = obj_to_params[obj_name]
1007 names += desired
1008 return names
1009
1010 def get_msg(self, message: Any) -> AcsMsgAndTransition:
1011 """ Respond with GetParameterValuesRequest """
1012 names = self.get_params_to_get(self.acs.data_model,)
1013
1014 # Generate the request
1015 request = models.GetParameterValues()
1016 request.ParameterNames = models.ParameterNames()
1017 request.ParameterNames.arrayType = "xsd:string[%d]" % len(names)
1018 request.ParameterNames.string = []
1019 for name in names:
1020 path = self.acs.data_model.get_parameter(name).path
1021 if path is not InvalidTrParamPath:
1022 # Only get data elements backed by tr69 path
1023 request.ParameterNames.string.append(path)
1024
1025 return AcsMsgAndTransition(msg=request, next_state=None)
1026
1027 def read_msg(self, message: Any) -> AcsReadMsgResult:
1028 """
1029 Process GetParameterValuesResponse
1030 """
1031 if not isinstance(message, models.GetParameterValuesResponse):
1032 return AcsReadMsgResult(msg_handled=False, next_state=None)
1033
1034 path_to_val = {}
1035 for param_value_struct in message.ParameterList.ParameterValueStruct:
1036 path_to_val[param_value_struct.Name] = param_value_struct.Value.Data
1037
1038 EnodebdLogger.debug("Received object parameters: %s", str(path_to_val))
1039
1040 # Parse simple params
1041 param_name_list = self.acs.data_model.get_parameter_names()
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +08001042
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001043 for name in param_name_list:
1044 path = self.acs.data_model.get_parameter(name).path
1045 if path in path_to_val:
1046 value = path_to_val.get(path)
1047 magma_val = self.acs.data_model.transform_for_magma(name, value,)
1048 self.acs.device_cfg.set_parameter(name, magma_val)
1049
1050 # Parse object params
1051 num_plmns = self.acs.data_model.get_num_plmns()
1052 for i in range(1, num_plmns + 1):
1053 obj_name = ParameterName.PLMN_N % i
1054 obj_to_params = self.acs.data_model.get_numbered_param_names()
1055 param_name_list = obj_to_params[obj_name]
1056 for name in param_name_list:
1057 path = self.acs.data_model.get_parameter(name).path
1058 if path in path_to_val:
1059 value = path_to_val.get(path)
1060 if value is None:
1061 continue
1062 if obj_name not in self.acs.device_cfg.get_object_names():
1063 self.acs.device_cfg.add_object(obj_name)
1064 magma_value = self.acs.data_model.transform_for_magma(name, value)
1065 self.acs.device_cfg.set_parameter_for_object(
1066 name, magma_value, obj_name,
1067 )
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +08001068
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001069 # Now we have enough information to build the desired configuration
1070 if self.acs.desired_cfg is None:
1071 self.acs.desired_cfg = build_desired_config(
1072 self.acs.mconfig,
1073 self.acs.service_config,
1074 self.acs.device_cfg,
1075 self.acs.data_model,
1076 self.acs.config_postprocessor,
1077 )
1078
1079 if (
1080 len(get_all_objects_to_delete(self.acs.desired_cfg, self.acs.device_cfg,),)
1081 > 0
1082 ):
1083 return AcsReadMsgResult(
1084 msg_handled=True, next_state=self.rm_obj_transition,
1085 )
1086 elif (
1087 len(get_all_objects_to_add(self.acs.desired_cfg, self.acs.device_cfg,),) > 0
1088 ):
1089 return AcsReadMsgResult(
1090 msg_handled=True, next_state=self.add_obj_transition,
1091 )
1092 elif (
1093 len(
1094 get_all_param_values_to_set(
1095 self.acs.desired_cfg, self.acs.device_cfg, self.acs.data_model,
1096 ),
1097 )
1098 > 0
1099 ):
1100 return AcsReadMsgResult(
1101 msg_handled=True, next_state=self.set_params_transition,
1102 )
1103 return AcsReadMsgResult(msg_handled=True, next_state=self.skip_transition,)
1104
1105 def state_description(self) -> str:
1106 return "Getting well known parameters"