blob: 8acb1520dcec71db63d5594f174458c18d2b71a7 [file] [log] [blame]
Wei-Yu Chenad55cb82022-02-15 20:07:01 +08001# SPDX-FileCopyrightText: 2020 The Magma Authors.
2# SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org>
3#
4# SPDX-License-Identifier: BSD-3-Clause
Wei-Yu Chen49950b92021-11-08 19:19:18 +08005
6import json
7from collections import namedtuple
8from typing import Any, Optional, Union
9
10from lte.protos.mconfig import mconfigs_pb2
11from common.misc_utils import get_ip_from_if
12from configuration.exceptions import LoadConfigError
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +080013from configuration.service_configs import load_enb_config, load_common_config
Wei-Yu Chen49950b92021-11-08 19:19:18 +080014from configuration.mconfig_managers import load_service_mconfig_as_json
15from data_models.data_model import DataModel
16from data_models.data_model_parameters import ParameterName
17from device_config.enodeb_config_postprocessor import (
18 EnodebConfigurationPostProcessor,
19)
20from device_config.enodeb_configuration import EnodebConfiguration
21from exceptions import ConfigurationError
22from logger import EnodebdLogger as logger
23from lte_utils import (
24 DuplexMode,
25 map_earfcndl_to_band_earfcnul_mode,
26 map_earfcndl_to_duplex_mode,
27)
28
29# LTE constants
30DEFAULT_S1_PORT = 36412
31# This is a known working value for supported eNB devices.
32# Cell Identity is a 28 bit number, but not all values are supported.
33DEFAULT_CELL_IDENTITY = 138777000
34
35SingleEnodebConfig = namedtuple(
36 'SingleEnodebConfig',
37 [
38 'earfcndl', 'subframe_assignment',
39 'special_subframe_pattern',
40 'pci', 'plmnid_list', 'tac',
41 'bandwidth_mhz', 'cell_id',
42 'allow_enodeb_transmit',
43 'mme_address', 'mme_port',
44 ],
45)
46
47
48def config_assert(condition: bool, message: str = None) -> None:
49 """ To be used in place of 'assert' so that ConfigurationError is raised
50 for all config-related exceptions. """
51 if not condition:
52 raise ConfigurationError(message)
53
54
55def build_desired_config(
56 mconfig: Any,
57 service_config: Any,
58 device_config: EnodebConfiguration,
59 data_model: DataModel,
60 post_processor: EnodebConfigurationPostProcessor,
61) -> EnodebConfiguration:
62 """
63 Factory for initializing DESIRED data model configuration.
64
65 When working with the configuration of an eNodeB, we track the
66 current state of configuration for that device, as well as what
67 configuration we want to set on the device.
68 Args:
69 mconfig: Managed configuration, eNodeB protobuf message
70 service_config:
71 Returns:
72 Desired data model configuration for the device
73 """
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +080074
75 print("DEVICE CFG: ", device_config)
76
Wei-Yu Chen49950b92021-11-08 19:19:18 +080077 cfg_desired = EnodebConfiguration(data_model)
78
79 # Determine configuration parameters
80 _set_management_server(cfg_desired)
81
82 # Attempt to load device configuration from YANG before service mconfig
83 enb_config = _get_enb_yang_config(device_config) or \
84 _get_enb_config(mconfig, device_config)
85
Wei-Yu Chen678f0a52021-12-21 13:50:52 +080086 print(enb_config)
87
Wei-Yu Chen49950b92021-11-08 19:19:18 +080088 _set_earfcn_freq_band_mode(
89 device_config, cfg_desired, data_model,
90 enb_config.earfcndl,
91 )
92 if enb_config.subframe_assignment is not None:
93 _set_tdd_subframe_config(
94 device_config, cfg_desired,
95 enb_config.subframe_assignment,
96 enb_config.special_subframe_pattern,
97 )
98 _set_pci(cfg_desired, enb_config.pci)
99 _set_plmnids_tac(cfg_desired, enb_config.plmnid_list, enb_config.tac)
100 _set_bandwidth(cfg_desired, data_model, enb_config.bandwidth_mhz)
101 _set_cell_id(cfg_desired, enb_config.cell_id)
102 _set_perf_mgmt(
103 cfg_desired,
104 get_ip_from_if(service_config['tr069']['interface']),
105 service_config['tr069']['perf_mgmt_port'],
106 )
107 _set_misc_static_params(device_config, cfg_desired, data_model)
108 if enb_config.mme_address is not None and enb_config.mme_port is not None:
109 _set_s1_connection(
110 cfg_desired,
111 enb_config.mme_address,
112 enb_config.mme_port,
113 )
114 else:
115 _set_s1_connection(
116 cfg_desired, get_ip_from_if(service_config['s1_interface']),
117 )
118
119 # Enable LTE if we should
120 cfg_desired.set_parameter(
121 ParameterName.ADMIN_STATE,
122 enb_config.allow_enodeb_transmit,
123 )
124
125 post_processor.postprocess(mconfig, service_config, cfg_desired)
126 return cfg_desired
127
128
129def _get_enb_yang_config(
130 device_config: EnodebConfiguration,
131) -> Optional[SingleEnodebConfig]:
132 """"
133 Proof of concept configuration function to load eNB configs from YANG
134 data model. Attempts to load configuration from YANG for the eNodeB if
135 an entry exists with a matching serial number.
136 Args:
137 device_config: eNodeB device configuration
138 Returns:
139 None or a SingleEnodebConfig from YANG with matching serial number
140 """
141 enb = []
142 mme_list = []
143 mme_address = None
144 mme_port = None
145 try:
146 enb_serial = \
147 device_config.get_parameter(ParameterName.SERIAL_NUMBER)
148 config = json.loads(
149 load_service_mconfig_as_json('yang').get('value', '{}'),
150 )
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800151
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800152 enb.extend(
153 filter(
154 lambda entry: entry['serial'] == enb_serial,
155 config.get('cellular', {}).get('enodeb', []),
156 ),
157 )
158 except (ValueError, KeyError, LoadConfigError):
159 return None
160 if len(enb) == 0:
161 return None
162 enb_config = enb[0].get('config', {})
163 mme_list.extend(enb_config.get('mme', []))
164 if len(mme_list) > 0:
165 mme_address = mme_list[0].get('host')
166 mme_port = mme_list[0].get('port')
167 single_enodeb_config = SingleEnodebConfig(
168 earfcndl=enb_config.get('earfcndl'),
169 subframe_assignment=enb_config.get('subframe_assignment'),
170 special_subframe_pattern=enb_config.get('special_subframe_pattern'),
171 pci=enb_config.get('pci'),
172 plmnid_list=",".join(enb_config.get('plmnid', [])),
173 tac=enb_config.get('tac'),
174 bandwidth_mhz=enb_config.get('bandwidth_mhz'),
175 cell_id=enb_config.get('cell_id'),
176 allow_enodeb_transmit=enb_config.get('transmit_enabled'),
177 mme_address=mme_address,
178 mme_port=mme_port,
179 )
180 return single_enodeb_config
181
182
183def _get_enb_config(
184 mconfig: mconfigs_pb2.EnodebD,
185 device_config: EnodebConfiguration,
186) -> SingleEnodebConfig:
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800187 # The eNodeB parameters to be generated with default value,
188 # It will load from eNB configs based on serial number or default value
189 # The params is a nested list which contains 2 format of parameter names.
190 # The first parameter is the name of eNB / ACS configuration in
191 # magma_configs/serial_number/ and magma_configs/acs_common.yml
192 # The second parameter is the name of gateway configuration in
193 # override_configs/gateway.mconfig
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800194
195 # params.column1 = SingleEnodebConfig key
196 # params.column2 = sn config
197 # params.column3 = common_config
198 # params.column4 = mconfig
199
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800200 params = [
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800201 ["earfcndl", "earfcn_downlink1", "earfcndl" * 2],
202 ["subframe_assignment", "subframe_assignment", "subframeAssignment", "subframe_assignment"],
203 ["special_subframe_pattern", "subframe_configuration", "specialSubframePattern", "special_subframe_pattern"],
204 ["pci", "pci1", "pci", "pci"],
205 ["plmnid_list", "plmnid_list", "plmnidList", "plmnid_list"],
206 ["tac", "tac1", "tac", "tac"],
207 ["bandwidth_mhz", "downlink_bandwidth", "bandwidthMhz", "bandwidth_mhz"],
208 # Note: mconfig doesn't have allowEnodebTransmit
209 ["allow_enodeb_transmit", "allow_enodeb_transmit", "allowEnodebTransmit", "allowEnodebTransmit"]
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800210 ]
211
212 extend_params = ["cell_id", "mme_address", "mme_port"]
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800213
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800214 params_dict = dict()
215
216 common_config = load_common_config()
217 enb_configs = load_enb_config()
218 enb_serial = device_config.get_parameter(ParameterName.SERIAL_NUMBER)
219 enb_config = enb_configs.get(enb_serial, dict())
220
221 for param in params:
Wei-Yu Chenb91af852022-03-15 22:24:49 +0800222 params_dict[param[0]] = enb_config.get(param[1],
223 common_config.get(param[0], mconfig.__getattribute__(param[0]))
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800224 )
225
226 for param in extend_params:
227 params_dict[param] = enb_config.get(param, common_config.get(param, None))
228
229 return SingleEnodebConfig(**params_dict)
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800230
231
232def _set_pci(
233 cfg: EnodebConfiguration,
234 pci: Any,
235) -> None:
236 """
237 Set the following parameters:
238 - PCI
239 """
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800240
241 if pci is int and pci not in range(0, 504 + 1):
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800242 raise ConfigurationError('Invalid PCI (%d)' % pci)
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800243
244 if pci is str and any(map(lambda x: int(x) not in range(0, 504 + 1), pci.split(","))):
245 raise ConfigurationError('Invalid PCI (%s)' % pci)
246
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800247 cfg.set_parameter(ParameterName.PCI, pci)
248
249
250def _set_bandwidth(
251 cfg: EnodebConfiguration,
252 data_model: DataModel,
253 bandwidth_mhz: Any,
254) -> None:
255 """
256 Set the following parameters:
257 - DL bandwidth
258 - UL bandwidth
259 """
260 _set_param_if_present(
261 cfg, data_model, ParameterName.DL_BANDWIDTH,
262 bandwidth_mhz,
263 )
264 _set_param_if_present(
265 cfg, data_model, ParameterName.UL_BANDWIDTH,
266 bandwidth_mhz,
267 )
268
269
270def _set_cell_id(
271 cfg: EnodebConfiguration,
272 cell_id: int,
273) -> None:
274 config_assert(
275 cell_id in range(0, 268435456),
276 'Cell Identity should be from 0 - (2^28 - 1)',
277 )
278 cfg.set_parameter(ParameterName.CELL_ID, cell_id)
279
280
281def _set_tdd_subframe_config(
282 device_cfg: EnodebConfiguration,
283 cfg: EnodebConfiguration,
284 subframe_assignment: Any,
285 special_subframe_pattern: Any,
286) -> None:
287 """
288 Set the following parameters:
289 - Subframe assignment
290 - Special subframe pattern
291 """
292 # Don't try to set if this is not TDD mode
293 if (
294 device_cfg.has_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
295 and device_cfg.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
296 != 'TDDMode'
297 ):
298 return
299
300 config_assert(
301 subframe_assignment in range(0, 6 + 1),
302 'Invalid TDD subframe assignment (%d)' % subframe_assignment,
303 )
304 config_assert(
305 special_subframe_pattern in range(0, 9 + 1),
306 'Invalid TDD special subframe pattern (%d)'
307 % special_subframe_pattern,
308 )
309
310 cfg.set_parameter(
311 ParameterName.SUBFRAME_ASSIGNMENT,
312 subframe_assignment,
313 )
314 cfg.set_parameter(
315 ParameterName.SPECIAL_SUBFRAME_PATTERN,
316 special_subframe_pattern,
317 )
318
319
320def _set_management_server(cfg: EnodebConfiguration) -> None:
321 """
322 Set the following parameters:
323 - Periodic inform enable
324 - Periodic inform interval (hard-coded)
325 """
326 cfg.set_parameter(ParameterName.PERIODIC_INFORM_ENABLE, True)
327 # In seconds
328 cfg.set_parameter(ParameterName.PERIODIC_INFORM_INTERVAL, 5)
329
330
331def _set_s1_connection(
332 cfg: EnodebConfiguration,
333 mme_ip: Any,
334 mme_port: Any = DEFAULT_S1_PORT,
335) -> None:
336 """
337 Set the following parameters:
338 - MME IP
339 - MME port (defalts to 36412 as per TR-196 recommendation)
340 """
341 config_assert(type(mme_ip) == str, 'Invalid MME IP type')
342 config_assert(type(mme_port) == int, 'Invalid MME Port type')
343 cfg.set_parameter(ParameterName.MME_IP, mme_ip)
344 cfg.set_parameter(ParameterName.MME_PORT, mme_port)
345
346
347def _set_perf_mgmt(
348 cfg: EnodebConfiguration,
349 perf_mgmt_ip: str,
350 perf_mgmt_port: int,
351) -> None:
352 """
353 Set the following parameters:
354 - Perf mgmt enable
355 - Perf mgmt upload interval
356 - Perf mgmt upload URL
357 """
358 cfg.set_parameter(ParameterName.PERF_MGMT_ENABLE, True)
359 # Upload interval supported values (in secs):
360 # [60, 300, 900, 1800, 3600]
361 # Note: eNodeB crashes have been experienced with 60-sec interval.
362 # Hence using 300sec
363 cfg.set_parameter(
364 ParameterName.PERF_MGMT_UPLOAD_INTERVAL,
365 300,
366 )
367 cfg.set_parameter(
368 ParameterName.PERF_MGMT_UPLOAD_URL,
369 'http://%s:%d/' % (perf_mgmt_ip, perf_mgmt_port),
370 )
371
372
373def _set_misc_static_params(
374 device_cfg: EnodebConfiguration,
375 cfg: EnodebConfiguration,
376 data_model: DataModel,
377) -> None:
378 """
379 Set the following parameters:
380 - Local gateway enable
381 - GPS enable
382 """
383 _set_param_if_present(
384 cfg, data_model, ParameterName.LOCAL_GATEWAY_ENABLE,
385 0,
386 )
387 _set_param_if_present(cfg, data_model, ParameterName.GPS_ENABLE, True)
388 # For BaiCells eNodeBs, IPSec enable may be either integer or bool.
389 # Set to false/0 depending on the current type
390 if data_model.is_parameter_present(ParameterName.IP_SEC_ENABLE):
391 try:
392 int(device_cfg.get_parameter(ParameterName.IP_SEC_ENABLE))
393 cfg.set_parameter(ParameterName.IP_SEC_ENABLE, value=0)
394 except ValueError:
395 cfg.set_parameter(ParameterName.IP_SEC_ENABLE, value=False)
396
397 _set_param_if_present(cfg, data_model, ParameterName.CELL_RESERVED, False)
398 _set_param_if_present(
399 cfg, data_model, ParameterName.MME_POOL_ENABLE,
400 False,
401 )
402
403
404def _set_plmnids_tac(
405 cfg: EnodebConfiguration,
406 plmnids: Union[int, str],
407 tac: Any,
408) -> None:
409 """
410 Set the following parameters:
411 - PLMNID list (including all child parameters)
412
413 Input 'plmnids' is comma-separated list of PLMNIDs
414 """
415 # Convert int PLMNID to string
416 if type(plmnids) == int:
417 plmnid_str = str(plmnids)
418 else:
419 config_assert(type(plmnids) == str, 'PLMNID must be string')
420 plmnid_str = plmnids
421
422 # Multiple PLMNIDs will be supported using comma-separated list.
423 # Currently, just one supported
424 for char in plmnid_str:
425 config_assert(
426 char in '0123456789, ',
427 'Unhandled character (%s) in PLMNID' % char,
428 )
429 plmnid_list = plmnid_str.split(',')
430
431 # TODO - add support for multiple PLMNIDs
432 config_assert(
433 len(plmnid_list) == 1,
434 'Exactly one PLMNID must be configured',
435 )
436
437 # Validate PLMNIDs
438 plmnid_list[0] = plmnid_list[0].strip()
439 config_assert(
440 len(plmnid_list[0]) <= 6,
441 'PLMNID must be length <=6 (%s)' % plmnid_list[0],
442 )
443
444 # We just need one PLMN element in the config. Delete all others.
445 for i in range(1, 2): # data_model.get_num_plmns() + 1):
446 object_name = ParameterName.PLMN_N % i
447 enable_plmn = i == 1
448 cfg.add_object(object_name)
449 cfg.set_parameter_for_object(
450 ParameterName.PLMN_N_ENABLE % i,
451 enable_plmn,
452 object_name,
453 )
454 if enable_plmn:
455 cfg.set_parameter_for_object(
456 ParameterName.PLMN_N_CELL_RESERVED % i,
457 False, object_name,
458 )
459 cfg.set_parameter_for_object(
460 ParameterName.PLMN_N_PRIMARY % i,
461 enable_plmn,
462 object_name,
463 )
464 cfg.set_parameter_for_object(
465 ParameterName.PLMN_N_PLMNID % i,
466 plmnid_list[i - 1],
467 object_name,
468 )
469 cfg.set_parameter(ParameterName.TAC, tac)
470
471
472def _set_earfcn_freq_band_mode(
473 device_cfg: EnodebConfiguration,
474 cfg: EnodebConfiguration,
475 data_model: DataModel,
476 earfcndl: int,
477) -> None:
478 """
479 Set the following parameters:
480 - EARFCNDL
481 - EARFCNUL
482 - Band
483 """
484 # Note: validation of EARFCNDL done by mapping function. If invalid
485 # EARFCN, raise ConfigurationError
486 try:
487 band, duplex_mode, earfcnul = map_earfcndl_to_band_earfcnul_mode(
488 earfcndl,
489 )
490 except ValueError as err:
491 raise ConfigurationError(err)
492
493 # Verify capabilities
494 if device_cfg.has_parameter(ParameterName.DUPLEX_MODE_CAPABILITY):
495 duplex_capability = \
496 device_cfg.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
497 if duplex_mode == DuplexMode.TDD and duplex_capability != 'TDDMode':
498 raise ConfigurationError((
499 'eNodeB duplex mode capability is <{0}>, '
500 'but earfcndl is <{1}>, giving duplex '
501 'mode <{2}> instead'
502 ).format(
503 duplex_capability, str(earfcndl), str(duplex_mode),
504 ))
505 elif duplex_mode == DuplexMode.FDD and duplex_capability != 'FDDMode':
506 raise ConfigurationError((
507 'eNodeB duplex mode capability is <{0}>, '
508 'but earfcndl is <{1}>, giving duplex '
509 'mode <{2}> instead'
510 ).format(
511 duplex_capability, str(earfcndl), str(duplex_mode),
512 ))
513 elif duplex_mode not in {DuplexMode.TDD, DuplexMode.FDD}:
514 raise ConfigurationError(
515 'Invalid duplex mode (%s)' % str(duplex_mode),
516 )
517
518 if device_cfg.has_parameter(ParameterName.BAND_CAPABILITY):
519 # Baicells indicated that they no longer use the band capability list,
520 # so it may not be populated correctly
521 band_capability_list = device_cfg.get_parameter(
522 ParameterName.BAND_CAPABILITY,
523 )
524 band_capabilities = band_capability_list.split(',')
525 if str(band) not in band_capabilities:
526 logger.warning(
527 'Band %d not in capabilities list (%s). Continuing'
528 ' with config because capabilities list may not be'
529 ' correct', band, band_capabilities,
530 )
531 cfg.set_parameter(ParameterName.EARFCNDL, earfcndl)
532 if duplex_mode == DuplexMode.FDD:
533 _set_param_if_present(
534 cfg, data_model, ParameterName.EARFCNUL,
535 earfcnul,
536 )
537 else:
538 logger.debug('Not setting EARFCNUL - duplex mode is not FDD')
539
540 _set_param_if_present(cfg, data_model, ParameterName.BAND, band)
541
542 if duplex_mode == DuplexMode.TDD:
543 logger.debug('Set EARFCNDL=%d, Band=%d', earfcndl, band)
544 elif duplex_mode == DuplexMode.FDD:
545 logger.debug(
546 'Set EARFCNDL=%d, EARFCNUL=%d, Band=%d',
547 earfcndl, earfcnul, band,
548 )
549
550
551def _set_param_if_present(
552 cfg: EnodebConfiguration,
553 data_model: DataModel,
554 param: ParameterName,
555 value: Any,
556) -> None:
557 if data_model.is_parameter_present(param):
558 cfg.set_parameter(param, value)