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