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