blob: 6139c063fa2a5e77aa4d405b0f7479dca40b535e [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
194 params = [
195 ["earfcndl", "earfcndl"],
196 ["subframeAssignment", "subframe_assignment"],
197 ["special_subframe_pattern", "special_subframe_pattern"],
198 ["pci", "pci"],
199 ["plmnidList", "plmnid_list"],
200 ["tac", "tac"],
201 ["bandwidthMhz", "bandwidth_mhz"],
202 ["allowEnodebTransmit", "allow_enodeb_transmit"]
203 ]
204
205 extend_params = ["cell_id", "mme_address", "mme_port"]
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800206
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +0800207 params_dict = dict()
208
209 common_config = load_common_config()
210 enb_configs = load_enb_config()
211 enb_serial = device_config.get_parameter(ParameterName.SERIAL_NUMBER)
212 enb_config = enb_configs.get(enb_serial, dict())
213
214 for param in params:
215 params_dict[param[1]] = enb_config.get(param[0],
216 common_config.get(param[0], mconfig.__getattribute__(param[1]))
217 )
218
219 for param in extend_params:
220 params_dict[param] = enb_config.get(param, common_config.get(param, None))
221
222 return SingleEnodebConfig(**params_dict)
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800223
224
225def _set_pci(
226 cfg: EnodebConfiguration,
227 pci: Any,
228) -> None:
229 """
230 Set the following parameters:
231 - PCI
232 """
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800233
234 if pci is int and pci not in range(0, 504 + 1):
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800235 raise ConfigurationError('Invalid PCI (%d)' % pci)
Wei-Yu Chen678f0a52021-12-21 13:50:52 +0800236
237 if pci is str and any(map(lambda x: int(x) not in range(0, 504 + 1), pci.split(","))):
238 raise ConfigurationError('Invalid PCI (%s)' % pci)
239
Wei-Yu Chen49950b92021-11-08 19:19:18 +0800240 cfg.set_parameter(ParameterName.PCI, pci)
241
242
243def _set_bandwidth(
244 cfg: EnodebConfiguration,
245 data_model: DataModel,
246 bandwidth_mhz: Any,
247) -> None:
248 """
249 Set the following parameters:
250 - DL bandwidth
251 - UL bandwidth
252 """
253 _set_param_if_present(
254 cfg, data_model, ParameterName.DL_BANDWIDTH,
255 bandwidth_mhz,
256 )
257 _set_param_if_present(
258 cfg, data_model, ParameterName.UL_BANDWIDTH,
259 bandwidth_mhz,
260 )
261
262
263def _set_cell_id(
264 cfg: EnodebConfiguration,
265 cell_id: int,
266) -> None:
267 config_assert(
268 cell_id in range(0, 268435456),
269 'Cell Identity should be from 0 - (2^28 - 1)',
270 )
271 cfg.set_parameter(ParameterName.CELL_ID, cell_id)
272
273
274def _set_tdd_subframe_config(
275 device_cfg: EnodebConfiguration,
276 cfg: EnodebConfiguration,
277 subframe_assignment: Any,
278 special_subframe_pattern: Any,
279) -> None:
280 """
281 Set the following parameters:
282 - Subframe assignment
283 - Special subframe pattern
284 """
285 # Don't try to set if this is not TDD mode
286 if (
287 device_cfg.has_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
288 and device_cfg.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
289 != 'TDDMode'
290 ):
291 return
292
293 config_assert(
294 subframe_assignment in range(0, 6 + 1),
295 'Invalid TDD subframe assignment (%d)' % subframe_assignment,
296 )
297 config_assert(
298 special_subframe_pattern in range(0, 9 + 1),
299 'Invalid TDD special subframe pattern (%d)'
300 % special_subframe_pattern,
301 )
302
303 cfg.set_parameter(
304 ParameterName.SUBFRAME_ASSIGNMENT,
305 subframe_assignment,
306 )
307 cfg.set_parameter(
308 ParameterName.SPECIAL_SUBFRAME_PATTERN,
309 special_subframe_pattern,
310 )
311
312
313def _set_management_server(cfg: EnodebConfiguration) -> None:
314 """
315 Set the following parameters:
316 - Periodic inform enable
317 - Periodic inform interval (hard-coded)
318 """
319 cfg.set_parameter(ParameterName.PERIODIC_INFORM_ENABLE, True)
320 # In seconds
321 cfg.set_parameter(ParameterName.PERIODIC_INFORM_INTERVAL, 5)
322
323
324def _set_s1_connection(
325 cfg: EnodebConfiguration,
326 mme_ip: Any,
327 mme_port: Any = DEFAULT_S1_PORT,
328) -> None:
329 """
330 Set the following parameters:
331 - MME IP
332 - MME port (defalts to 36412 as per TR-196 recommendation)
333 """
334 config_assert(type(mme_ip) == str, 'Invalid MME IP type')
335 config_assert(type(mme_port) == int, 'Invalid MME Port type')
336 cfg.set_parameter(ParameterName.MME_IP, mme_ip)
337 cfg.set_parameter(ParameterName.MME_PORT, mme_port)
338
339
340def _set_perf_mgmt(
341 cfg: EnodebConfiguration,
342 perf_mgmt_ip: str,
343 perf_mgmt_port: int,
344) -> None:
345 """
346 Set the following parameters:
347 - Perf mgmt enable
348 - Perf mgmt upload interval
349 - Perf mgmt upload URL
350 """
351 cfg.set_parameter(ParameterName.PERF_MGMT_ENABLE, True)
352 # Upload interval supported values (in secs):
353 # [60, 300, 900, 1800, 3600]
354 # Note: eNodeB crashes have been experienced with 60-sec interval.
355 # Hence using 300sec
356 cfg.set_parameter(
357 ParameterName.PERF_MGMT_UPLOAD_INTERVAL,
358 300,
359 )
360 cfg.set_parameter(
361 ParameterName.PERF_MGMT_UPLOAD_URL,
362 'http://%s:%d/' % (perf_mgmt_ip, perf_mgmt_port),
363 )
364
365
366def _set_misc_static_params(
367 device_cfg: EnodebConfiguration,
368 cfg: EnodebConfiguration,
369 data_model: DataModel,
370) -> None:
371 """
372 Set the following parameters:
373 - Local gateway enable
374 - GPS enable
375 """
376 _set_param_if_present(
377 cfg, data_model, ParameterName.LOCAL_GATEWAY_ENABLE,
378 0,
379 )
380 _set_param_if_present(cfg, data_model, ParameterName.GPS_ENABLE, True)
381 # For BaiCells eNodeBs, IPSec enable may be either integer or bool.
382 # Set to false/0 depending on the current type
383 if data_model.is_parameter_present(ParameterName.IP_SEC_ENABLE):
384 try:
385 int(device_cfg.get_parameter(ParameterName.IP_SEC_ENABLE))
386 cfg.set_parameter(ParameterName.IP_SEC_ENABLE, value=0)
387 except ValueError:
388 cfg.set_parameter(ParameterName.IP_SEC_ENABLE, value=False)
389
390 _set_param_if_present(cfg, data_model, ParameterName.CELL_RESERVED, False)
391 _set_param_if_present(
392 cfg, data_model, ParameterName.MME_POOL_ENABLE,
393 False,
394 )
395
396
397def _set_plmnids_tac(
398 cfg: EnodebConfiguration,
399 plmnids: Union[int, str],
400 tac: Any,
401) -> None:
402 """
403 Set the following parameters:
404 - PLMNID list (including all child parameters)
405
406 Input 'plmnids' is comma-separated list of PLMNIDs
407 """
408 # Convert int PLMNID to string
409 if type(plmnids) == int:
410 plmnid_str = str(plmnids)
411 else:
412 config_assert(type(plmnids) == str, 'PLMNID must be string')
413 plmnid_str = plmnids
414
415 # Multiple PLMNIDs will be supported using comma-separated list.
416 # Currently, just one supported
417 for char in plmnid_str:
418 config_assert(
419 char in '0123456789, ',
420 'Unhandled character (%s) in PLMNID' % char,
421 )
422 plmnid_list = plmnid_str.split(',')
423
424 # TODO - add support for multiple PLMNIDs
425 config_assert(
426 len(plmnid_list) == 1,
427 'Exactly one PLMNID must be configured',
428 )
429
430 # Validate PLMNIDs
431 plmnid_list[0] = plmnid_list[0].strip()
432 config_assert(
433 len(plmnid_list[0]) <= 6,
434 'PLMNID must be length <=6 (%s)' % plmnid_list[0],
435 )
436
437 # We just need one PLMN element in the config. Delete all others.
438 for i in range(1, 2): # data_model.get_num_plmns() + 1):
439 object_name = ParameterName.PLMN_N % i
440 enable_plmn = i == 1
441 cfg.add_object(object_name)
442 cfg.set_parameter_for_object(
443 ParameterName.PLMN_N_ENABLE % i,
444 enable_plmn,
445 object_name,
446 )
447 if enable_plmn:
448 cfg.set_parameter_for_object(
449 ParameterName.PLMN_N_CELL_RESERVED % i,
450 False, object_name,
451 )
452 cfg.set_parameter_for_object(
453 ParameterName.PLMN_N_PRIMARY % i,
454 enable_plmn,
455 object_name,
456 )
457 cfg.set_parameter_for_object(
458 ParameterName.PLMN_N_PLMNID % i,
459 plmnid_list[i - 1],
460 object_name,
461 )
462 cfg.set_parameter(ParameterName.TAC, tac)
463
464
465def _set_earfcn_freq_band_mode(
466 device_cfg: EnodebConfiguration,
467 cfg: EnodebConfiguration,
468 data_model: DataModel,
469 earfcndl: int,
470) -> None:
471 """
472 Set the following parameters:
473 - EARFCNDL
474 - EARFCNUL
475 - Band
476 """
477 # Note: validation of EARFCNDL done by mapping function. If invalid
478 # EARFCN, raise ConfigurationError
479 try:
480 band, duplex_mode, earfcnul = map_earfcndl_to_band_earfcnul_mode(
481 earfcndl,
482 )
483 except ValueError as err:
484 raise ConfigurationError(err)
485
486 # Verify capabilities
487 if device_cfg.has_parameter(ParameterName.DUPLEX_MODE_CAPABILITY):
488 duplex_capability = \
489 device_cfg.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
490 if duplex_mode == DuplexMode.TDD and duplex_capability != 'TDDMode':
491 raise ConfigurationError((
492 'eNodeB duplex mode capability is <{0}>, '
493 'but earfcndl is <{1}>, giving duplex '
494 'mode <{2}> instead'
495 ).format(
496 duplex_capability, str(earfcndl), str(duplex_mode),
497 ))
498 elif duplex_mode == DuplexMode.FDD and duplex_capability != 'FDDMode':
499 raise ConfigurationError((
500 'eNodeB duplex mode capability is <{0}>, '
501 'but earfcndl is <{1}>, giving duplex '
502 'mode <{2}> instead'
503 ).format(
504 duplex_capability, str(earfcndl), str(duplex_mode),
505 ))
506 elif duplex_mode not in {DuplexMode.TDD, DuplexMode.FDD}:
507 raise ConfigurationError(
508 'Invalid duplex mode (%s)' % str(duplex_mode),
509 )
510
511 if device_cfg.has_parameter(ParameterName.BAND_CAPABILITY):
512 # Baicells indicated that they no longer use the band capability list,
513 # so it may not be populated correctly
514 band_capability_list = device_cfg.get_parameter(
515 ParameterName.BAND_CAPABILITY,
516 )
517 band_capabilities = band_capability_list.split(',')
518 if str(band) not in band_capabilities:
519 logger.warning(
520 'Band %d not in capabilities list (%s). Continuing'
521 ' with config because capabilities list may not be'
522 ' correct', band, band_capabilities,
523 )
524 cfg.set_parameter(ParameterName.EARFCNDL, earfcndl)
525 if duplex_mode == DuplexMode.FDD:
526 _set_param_if_present(
527 cfg, data_model, ParameterName.EARFCNUL,
528 earfcnul,
529 )
530 else:
531 logger.debug('Not setting EARFCNUL - duplex mode is not FDD')
532
533 _set_param_if_present(cfg, data_model, ParameterName.BAND, band)
534
535 if duplex_mode == DuplexMode.TDD:
536 logger.debug('Set EARFCNDL=%d, Band=%d', earfcndl, band)
537 elif duplex_mode == DuplexMode.FDD:
538 logger.debug(
539 'Set EARFCNDL=%d, EARFCNUL=%d, Band=%d',
540 earfcndl, earfcnul, band,
541 )
542
543
544def _set_param_if_present(
545 cfg: EnodebConfiguration,
546 data_model: DataModel,
547 param: ParameterName,
548 value: Any,
549) -> None:
550 if data_model.is_parameter_present(param):
551 cfg.set_parameter(param, value)