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