Пример #1
0
def get_param_values_to_set(
    desired_cfg: EnodebConfiguration,
    device_cfg: EnodebConfiguration,
    data_model: DataModel,
    exclude_admin: bool = False,
) -> Dict[ParameterName, Any]:
    """
    Get a map of param names to values for parameters that we will
    set on the eNB's configuration, excluding parameters for objects that can
    be added/removed.

    Also exclude special parameters like admin state, since it may be set at
    a different time in the provisioning process than most parameters.
    """
    param_values = {}
    # Get the parameters we might set
    params = set(desired_cfg.get_parameter_names()) - set(READ_ONLY_PARAMETERS)
    if exclude_admin:
        params = set(params) - {ParameterName.ADMIN_STATE}
    # Values of parameters
    for name in params:
        new = desired_cfg.get_parameter(name)
        old = device_cfg.get_parameter(name)
        _type = data_model.get_parameter(name).type
        if not are_tr069_params_equal(new, old, _type):
            param_values[name] = new

    return param_values
Пример #2
0
def _set_tdd_subframe_config(
    device_cfg: EnodebConfiguration,
    cfg: EnodebConfiguration,
    subframe_assignment: Any,
    special_subframe_pattern: Any,
) -> None:
    """
    Set the following parameters:
     - Subframe assignment
     - Special subframe pattern
    """
    # Don't try to set if this is not TDD mode
    if device_cfg.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY) != \
            'TDDMode':
        return

    config_assert(subframe_assignment in range(0, 6 + 1),
                  'Invalid TDD subframe assignment (%d)' % subframe_assignment)
    config_assert(
        special_subframe_pattern in range(0, 9 + 1),
        'Invalid TDD special subframe pattern (%d)' % special_subframe_pattern)

    cfg.set_parameter(ParameterName.SUBFRAME_ASSIGNMENT, subframe_assignment)
    cfg.set_parameter(ParameterName.SPECIAL_SUBFRAME_PATTERN,
                      special_subframe_pattern)
Пример #3
0
def get_object_params_to_get(
    desired_cfg: Optional[EnodebConfiguration],
    device_cfg: EnodebConfiguration,
    data_model: DataModel,
    request_all_params: bool = False,
) -> List[ParameterName]:
    """
    Returns a list of parameter names for object parameters we don't know the
    current value of

    If `request_all_params` is set to True, the function will return a list
    of all device model param names, including already known ones.
    """
    names = []
    # TODO: This might a string for some strange reason, investigate why
    num_plmns = \
        int(device_cfg.get_parameter(ParameterName.NUM_PLMNS))
    for i in range(1, num_plmns + 1):
        obj_name = ParameterName.PLMN_N % i
        if not device_cfg.has_object(obj_name):
            device_cfg.add_object(obj_name)
        obj_to_params = data_model.get_numbered_param_names()
        desired = obj_to_params[obj_name]
        if request_all_params:
            names += desired
        else:
            current = []
            if desired_cfg is not None:
                current = desired_cfg.get_parameter_names_for_object(obj_name)
            names_to_add = list(set(desired) - set(current))
            names += names_to_add
    return names
Пример #4
0
def _set_misc_static_params(
    device_cfg: EnodebConfiguration,
    cfg: EnodebConfiguration,
    data_model: DataModel,
) -> None:
    """
    Set the following parameters:
     - Local gateway enable
     - GPS enable
    """
    _set_param_if_present(
        cfg,
        data_model,
        ParameterName.LOCAL_GATEWAY_ENABLE,
        0,
    )
    _set_param_if_present(cfg, data_model, ParameterName.GPS_ENABLE, True)
    # For BaiCells eNodeBs, IPSec enable may be either integer or bool.
    # Set to false/0 depending on the current type
    if data_model.is_parameter_present(ParameterName.IP_SEC_ENABLE):
        try:
            int(device_cfg.get_parameter(ParameterName.IP_SEC_ENABLE))
            cfg.set_parameter(ParameterName.IP_SEC_ENABLE, value=0)
        except ValueError:
            cfg.set_parameter(ParameterName.IP_SEC_ENABLE, value=False)

    _set_param_if_present(cfg, data_model, ParameterName.CELL_RESERVED, False)
    _set_param_if_present(
        cfg,
        data_model,
        ParameterName.MME_POOL_ENABLE,
        False,
    )
Пример #5
0
def get_object_params_to_get(
    desired_cfg: Optional[EnodebConfiguration],
    device_cfg: EnodebConfiguration,
    data_model: DataModel,
) -> List[ParameterName]:
    """
    Returns a list of parameter names for object parameters we don't know the
    current value of
    """
    names = []
    # TODO: This might a string for some strange reason, investigate why
    num_plmns = \
        int(device_cfg.get_parameter(ParameterName.NUM_PLMNS))
    for i in range(1, num_plmns + 1):
        obj_name = ParameterName.PLMN_N % i
        if not device_cfg.has_object(obj_name):
            device_cfg.add_object(obj_name)
        obj_to_params = data_model.get_numbered_param_names()
        desired = obj_to_params[obj_name]
        current = []
        if desired_cfg is not None:
            current = desired_cfg.get_parameter_names_for_object(obj_name)
        names_to_add = list(set(desired) - set(current))
        names = names + names_to_add
    return names
Пример #6
0
 def assert_config_updated(self, config: EnodebConfiguration,
                           bandwidth: str, earfcn: int, eirp: int) -> None:
     expected_values = {
         ParameterName.SAS_RADIO_ENABLE: True,
         ParameterName.DL_BANDWIDTH: bandwidth,
         ParameterName.UL_BANDWIDTH: bandwidth,
         ParameterName.EARFCNDL: earfcn,
         ParameterName.EARFCNUL: earfcn,
         ParameterName.POWER_SPECTRAL_DENSITY: eirp,
         ParameterName.BAND: 48,
     }
     for key, value in expected_values.items():
         self.assertEqual(config.get_parameter(key), value)
Пример #7
0
def _get_enb_yang_config(
        device_config: EnodebConfiguration,
) -> Optional[SingleEnodebConfig]:
    """"
    Proof of concept configuration function to load eNB configs from YANG
    data model. Attempts to load configuration from YANG for the eNodeB if
    an entry exists with a matching serial number.
    Args:
        device_config: eNodeB device configuration
    Returns:
        None or a SingleEnodebConfig from YANG with matching serial number
    """
    enb = []
    mme_list = []
    mme_address = None
    mme_port = None
    try:
        enb_serial = \
            device_config.get_parameter(ParameterName.SERIAL_NUMBER)
        config = json.loads(
            load_service_mconfig_as_json('yang').get('value', '{}'),
        )
        enb.extend(
            filter(
                lambda entry: entry['serial'] == enb_serial,
                config.get('cellular', {}).get('enodeb', []),
            ),
        )
    except (ValueError, KeyError, LoadConfigError):
        return None
    if len(enb) == 0:
        return None
    enb_config = enb[0].get('config', {})
    mme_list.extend(enb_config.get('mme', []))
    if len(mme_list) > 0:
        mme_address = mme_list[0].get('host')
        mme_port = mme_list[0].get('port')
    single_enodeb_config = SingleEnodebConfig(
        earfcndl=enb_config.get('earfcndl'),
        subframe_assignment=enb_config.get('subframe_assignment'),
        special_subframe_pattern=enb_config.get('special_subframe_pattern'),
        pci=enb_config.get('pci'),
        plmnid_list=",".join(enb_config.get('plmnid', [])),
        tac=enb_config.get('tac'),
        bandwidth_mhz=enb_config.get('bandwidth_mhz'),
        cell_id=enb_config.get('cell_id'),
        allow_enodeb_transmit=enb_config.get('transmit_enabled'),
        mme_address=mme_address,
        mme_port=mme_port,
    )
    return single_enodeb_config
Пример #8
0
 def test_omit_other_params_when_radio_disabled(self) -> None:
     config = EnodebConfiguration(BaicellsQRTBTrDataModel())
     channel = LteChannel(
         low_frequency_hz=3550_000_000,
         high_frequency_hz=3560_000_000,
         max_eirp_dbm_mhz=-100,
     )
     state = CBSDStateResult(
         radio_enabled=False,
         channel=channel,
     )
     qrtb_update_desired_config_from_cbsd_state(state, config)
     self.assertEqual(
         config.get_parameter(ParameterName.SAS_RADIO_ENABLE, ),
         False,
     )
Пример #9
0
    def test_tx_params_not_set_when_radio_disabled(self):
        """Test that tx parameters of the enodeb are not set when ADMIN_STATE is disabled on the radio"""
        desired_config = EnodebConfiguration(FreedomFiOneTrDataModel())
        channel = LteChannel(
            low_frequency_hz=3550000000,
            high_frequency_hz=3570000000,
            max_eirp_dbm_mhz=20,
        )
        state = CBSDStateResult(
            radio_enabled=False,
            channel=channel,
        )

        ff_one_update_desired_config_from_cbsd_state(state, desired_config)
        self.assertEqual(1, len(desired_config.get_parameter_names()))
        self.assertFalse(
            desired_config.get_parameter(ParameterName.ADMIN_STATE))
Пример #10
0
 def _assert_config_updated(
     self,
     config: EnodebConfiguration,
     bandwidth: str,
     earfcn: int,
     tx_power: int,
     radio_enabled: bool,
 ) -> None:
     expected_values = {
         ParameterName.ADMIN_STATE: radio_enabled,
         ParameterName.DL_BANDWIDTH: bandwidth,
         ParameterName.UL_BANDWIDTH: bandwidth,
         ParameterName.EARFCNDL: earfcn,
         ParameterName.EARFCNUL: earfcn,
         SASParameters.TX_POWER_CONFIG: tx_power,
         SASParameters.FREQ_BAND1: BAND,
         SASParameters.FREQ_BAND2: BAND,
     }
     for key, value in expected_values.items():
         self.assertEqual(config.get_parameter(key), value)
Пример #11
0
class EnodebConfigurationFactoryTest(TestCase):
    def setUp(self):
        self.data_model = BaicellsTrDataModel()
        self.cfg = EnodebConfiguration(BaicellsTrDataModel())
        self.device_cfg = EnodebConfiguration(BaicellsTrDataModel())

    def tearDown(self):
        self.data_model = None
        self.cfg = None
        self.device_cfg = None

    def test_set_pci(self):
        pci = 3
        _set_pci(self.cfg, pci)
        self.assertEqual(self.cfg.get_parameter(ParameterName.PCI), pci,
                         'PCI value should be same as what was set')
        with self.assertRaises(ConfigurationError):
            _set_pci(self.cfg, 505)

    def test_set_bandwidth(self):
        mhz = 15
        _set_bandwidth(self.cfg, self.data_model, mhz)
        self.assertEqual(self.cfg.get_parameter(ParameterName.DL_BANDWIDTH),
                         mhz,
                         'Should have set %s' % ParameterName.DL_BANDWIDTH)
        self.assertEqual(self.cfg.get_parameter(ParameterName.UL_BANDWIDTH),
                         mhz,
                         'Should have set %s' % ParameterName.UL_BANDWIDTH)

    def test_set_tdd_subframe_config(self):
        # Not TDD mode, should not try to set anything
        self.device_cfg.set_parameter(ParameterName.DUPLEX_MODE_CAPABILITY,
                                      'Not TDDMode')
        subframe = 0
        special_subframe = 0
        _set_tdd_subframe_config(self.device_cfg, self.cfg, subframe,
                                 special_subframe)
        self.assertTrue(ParameterName.SUBFRAME_ASSIGNMENT not in
                        self.cfg.get_parameter_names())

        # Invalid subframe assignment
        self.device_cfg.set_parameter(ParameterName.DUPLEX_MODE_CAPABILITY,
                                      'TDDMode')
        _set_tdd_subframe_config(self.device_cfg, self.cfg, subframe,
                                 special_subframe)
        self.assertIn(ParameterName.SUBFRAME_ASSIGNMENT,
                      self.cfg.get_parameter_names(),
                      'Expected a subframe assignment')

    def test_set_management_server(self):
        _set_management_server(self.cfg)
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.PERIODIC_INFORM_ENABLE), True,
            'Expected periodic inform enable to be true')
        self.assertTrue(
            isinstance(
                self.cfg.get_parameter(ParameterName.PERIODIC_INFORM_INTERVAL),
                int), 'Expected periodic inform interval to ani integer')

    def test_set_s1_connection(self):
        invalid_mme_ip = 1234
        invalid_mme_port = '8080'
        mme_ip = '192.168.0.1'
        mme_port = 8080

        # MME IP should be a string
        with self.assertRaises(ConfigurationError):
            _set_s1_connection(self.cfg, invalid_mme_ip, mme_port)

        # MME Port should be an integer
        with self.assertRaises(ConfigurationError):
            _set_s1_connection(self.cfg, mme_ip, invalid_mme_port)

        # Check the ip and port are sort properly
        _set_s1_connection(self.cfg, mme_ip, mme_port)
        self.assertEqual(self.cfg.get_parameter(ParameterName.MME_IP), mme_ip,
                         'Expected mme ip to be set')
        self.assertEqual(self.cfg.get_parameter(ParameterName.MME_PORT),
                         mme_port, 'Expected mme port to be set')

    def test_set_perf_mgmt(self):
        mgmt_ip = '192.168.0.1'
        mgmt_upload_interval = 300
        mgmt_port = 8080
        _set_perf_mgmt(self.cfg, mgmt_ip, mgmt_port)
        self.assertTrue(self.cfg.get_parameter(ParameterName.PERF_MGMT_ENABLE),
                        'Expected perf mgmt to be enabled')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.PERF_MGMT_UPLOAD_INTERVAL),
            mgmt_upload_interval, 'Expected upload interval to be set')
        expected_url = 'http://192.168.0.1:8080/'
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.PERF_MGMT_UPLOAD_URL),
            expected_url, 'Incorrect Url')

    def test_set_misc_static_params(self):
        # IPSec enable as integer
        self.device_cfg.set_parameter(ParameterName.IP_SEC_ENABLE, 0)
        self.data_model.set_parameter_presence(ParameterName.GPS_ENABLE, True)
        _set_misc_static_params(self.device_cfg, self.cfg, self.data_model)
        self.assertTrue(
            isinstance(self.cfg.get_parameter(ParameterName.IP_SEC_ENABLE),
                       int),
            'Should support an integer IP_SEC_ENABLE parameter')

        # IPSec enable as boolean
        self.device_cfg.set_parameter(ParameterName.IP_SEC_ENABLE, 'False')
        _set_misc_static_params(self.device_cfg, self.cfg, self.data_model)
        self.assertTrue(
            isinstance(self.cfg.get_parameter(ParameterName.IP_SEC_ENABLE),
                       bool),
            'Should support a boolean IP_SEC_ENABLE parameter')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.LOCAL_GATEWAY_ENABLE), 0,
            'Should be disabled')
        self.assertEqual(self.cfg.get_parameter(ParameterName.CELL_RESERVED),
                         False, 'Should be disabled')
        self.assertEqual(self.cfg.get_parameter(ParameterName.MME_POOL_ENABLE),
                         False, 'Should be disabled')

    def test_set_plmnids_tac(self):
        # We only handle a single PLMNID for now
        plmnids = '1, 2, 3, 4'
        tac = 1
        with self.assertRaises(ConfigurationError):
            _set_plmnids_tac(self.cfg, plmnids, tac)

        # Max PLMNID length is 6 characters
        plmnids = '1234567'
        with self.assertRaises(ConfigurationError):
            _set_plmnids_tac(self.cfg, plmnids, tac)

        # Check that only one PLMN element is enabled
        plmnids = '1'
        _set_plmnids_tac(self.cfg, plmnids, tac)
        self.assertTrue(
            self.cfg.get_parameter_for_object(ParameterName.PLMN_N_ENABLE % 1,
                                              ParameterName.PLMN_N % 1),
            'First PLMN should be enabled')
        self.assertFalse(self.cfg.has_object(ParameterName.PLMN_N % 2),
                         'Second PLMN should be disabled')

    def test_set_earafcn_freq_band_mode(self):
        # Invalid earfcndl
        with self.assertRaises(ConfigurationError):
            invalid_earfcndl = -1
            _set_earfcn_freq_band_mode(self.device_cfg, self.cfg,
                                       self.data_model, invalid_earfcndl)

        # Duplex_mode is TDD but capability is FDD
        with self.assertRaises(ConfigurationError):
            self.device_cfg.set_parameter(ParameterName.DUPLEX_MODE_CAPABILITY,
                                          'FDDMode')
            earfcndl = 38650  # Corresponds to TDD
            _set_earfcn_freq_band_mode(self.device_cfg, self.cfg,
                                       self.data_model, earfcndl)

        # Duplex_mode is FDD but capability is TDD
        with self.assertRaises(ConfigurationError):
            self.device_cfg.set_parameter(ParameterName.DUPLEX_MODE_CAPABILITY,
                                          'TDDMode')
            earfcndl = 0  # Corresponds to FDD
            _set_earfcn_freq_band_mode(self.device_cfg, self.cfg,
                                       self.data_model, earfcndl)

    def test_get_enb_config(self):
        mconfig = EnodebConfigBuilder.get_mconfig()
        enb_config = _get_enb_config(mconfig, self.device_cfg)
        self.assertTrue(enb_config.earfcndl == 39150,
                        "Should give earfcndl from default eNB config")

        mconfig = EnodebConfigBuilder.get_multi_enb_mconfig()
        self.device_cfg.set_parameter(ParameterName.SERIAL_NUMBER,
                                      '120200002618AGP0003')
        enb_config = _get_enb_config(mconfig, self.device_cfg)
        self.assertTrue(enb_config.earfcndl == 39151,
                        "Should give earfcndl from specific eNB config")
class EnodebConfigurationTest(TestCase):
    def setUp(self):
        self.config = EnodebConfiguration(CaviumTrDataModel)

    def tearDown(self):
        self.config = None

    def test_data_model(self) -> None:
        data_model = self.config.data_model
        expected = CaviumTrDataModel
        self.assertEqual(data_model, expected, 'Data model fetch incorrect')

    def test_get_has_set_parameter(self) -> None:
        param = ParameterName.ADMIN_STATE
        self.config.set_parameter(param, True)
        self.assertTrue(self.config.has_parameter(param),
                        'Expected to have parameter')
        param_value = self.config.get_parameter(param)
        expected = True
        self.assertEqual(param_value, expected,
                         'Parameter value does not match what was set')

    def test_add_has_delete_object(self) -> None:
        object_name = ParameterName.PLMN_N % 1
        self.assertFalse(self.config.has_object(object_name))
        self.config.add_object(object_name)
        self.assertTrue(self.config.has_object(object_name))
        self.config.delete_object(object_name)
        self.assertFalse(self.config.has_object(object_name))

    def test_get_parameter_names(self) -> None:
        # Should start off as an empty list
        names_list = self.config.get_parameter_names()
        self.assertEqual(len(names_list), 0, 'Expected 0 names')

        # Should grow as we set parameters
        self.config.set_parameter(ParameterName.ADMIN_STATE, True)
        names_list = self.config.get_parameter_names()
        self.assertEqual(len(names_list), 1, 'Expected 1 name')

        # Parameter names should not include objects
        self.config.add_object(ParameterName.PLMN)
        names_list = self.config.get_parameter_names()
        self.assertEqual(len(names_list), 1, 'Expected 1 name')

    def test_get_object_names(self) -> None:
        # Should start off as an empty list
        obj_list = self.config.get_object_names()
        self.assertEqual(len(obj_list), 0, 'Expected 0 names')

        # Should grow as we set parameters
        self.config.add_object(ParameterName.PLMN)
        obj_list = self.config.get_object_names()
        self.assertEqual(len(obj_list), 1, 'Expected 1 names')

    def test_get_set_parameter_for_object(self) -> None:
        self.config.add_object(ParameterName.PLMN_N % 1)
        self.config.set_parameter_for_object(
            ParameterName.PLMN_N_CELL_RESERVED % 1, True,
            ParameterName.PLMN_N % 1)
        param_value = self.config.get_parameter_for_object(
            ParameterName.PLMN_N_CELL_RESERVED % 1, ParameterName.PLMN_N % 1)
        self.assertTrue(
            param_value,
            'Expected that the param for object was set correctly')

    def test_get_parameter_names_for_object(self) -> None:
        # Should start off empty
        self.config.add_object(ParameterName.PLMN_N % 1)
        param_list = self.config.get_parameter_names_for_object(
            ParameterName.PLMN_N % 1)
        self.assertEqual(len(param_list), 0, 'Should be an empty param list')

        # Should increment as we set parameters
        self.config.set_parameter_for_object(
            ParameterName.PLMN_N_CELL_RESERVED % 1, True,
            ParameterName.PLMN_N % 1)
        param_list = self.config.get_parameter_names_for_object(
            ParameterName.PLMN_N % 1)
        self.assertEqual(len(param_list), 1, 'Should not be an empty list')
Пример #13
0
def qrtb_update_desired_config_from_cbsd_state(state: CBSDStateResult, config: EnodebConfiguration) -> None:
    """
    Call grpc endpoint on the Domain Proxy to update the desired config based on sas grant

    Args:
        state (CBSDStateResult): state result as received from DP
        config (EnodebConfiguration): configuration to update
    """
    logger.debug("Updating desired config based on Domain Proxy state.")
    num_of_channels = len(state.channels)
    radio_enabled = num_of_channels > 0 and state.radio_enabled
    config.set_parameter(ParameterName.SAS_RADIO_ENABLE, radio_enabled)

    if not radio_enabled:
        return

    # FAPService.1
    channel = state.channels[0]
    earfcn = calc_earfcn(channel.low_frequency_hz, channel.high_frequency_hz)
    bandwidth_mhz = calc_bandwidth_mhz(channel.low_frequency_hz, channel.high_frequency_hz)
    bandwidth_rbs = calc_bandwidth_rbs(bandwidth_mhz)
    psd = _calc_psd(channel.max_eirp_dbm_mhz)
    logger.debug(f"Channel1: {earfcn=}, {bandwidth_rbs=}, {psd=}")

    can_enable_carrier_aggregation = _qrtb_check_state_compatibility_with_ca(state)
    logger.debug(f"Should Carrier Aggregation be enabled on eNB: {can_enable_carrier_aggregation=}")
    # Enabling Carrier Aggregation on QRTB eNB means:
    # 1. Set CA_ENABLE to 1
    # 2. Set CA_NUM_OF_CELLS to 2
    # 3. Configure appropriate TR nodes for FAPSerivce.2 like EARFCNDL/UL etc
    # Otherwise we need to disable Carrier Aggregation on eNB and switch to Single Carrier configuration
    # 1. Set CA_ENABLE to 0
    # 2. Set CA_NUM_OF_CELLS to 1
    # Those two nodes should handle everything else accordingly.
    # (NOTE: carrier aggregation may still be enabled on Domain Proxy, but Domain Proxy may not have 2 channels granted by SAS.
    #        In such case, we still have to switch eNB to Single Carrier)
    num_of_cells = 2 if can_enable_carrier_aggregation else 1
    ca_enable = 1 if can_enable_carrier_aggregation else 0

    params_to_set = {
        ParameterName.SAS_RADIO_ENABLE: True,
        ParameterName.BAND: BAND,
        ParameterName.DL_BANDWIDTH: bandwidth_rbs,
        ParameterName.UL_BANDWIDTH: bandwidth_rbs,
        ParameterName.EARFCNDL: earfcn,
        ParameterName.EARFCNUL: earfcn,
        ParameterName.POWER_SPECTRAL_DENSITY: psd,
        CarrierAggregationParameters.CA_ENABLE: ca_enable,
        CarrierAggregationParameters.CA_NUM_OF_CELLS: num_of_cells,
    }
    if can_enable_carrier_aggregation:
        # Configure FAPService.2
        # NOTE: We set PCI and CELL_ID to the values of FAP1 "+1"
        #       This was suggested by BaiCells
        channel = state.channels[1]
        earfcn = calc_earfcn(channel.low_frequency_hz, channel.high_frequency_hz)
        bandwidth_mhz = calc_bandwidth_mhz(channel.low_frequency_hz, channel.high_frequency_hz)
        bandwidth_rbs = calc_bandwidth_rbs(bandwidth_mhz)
        psd = _calc_psd(channel.max_eirp_dbm_mhz)
        logger.debug(f"Channel2: {earfcn=}, {bandwidth_rbs=}, {psd=}")
        params_to_set.update({
            CarrierAggregationParameters.CA_DL_BANDWIDTH: bandwidth_rbs,
            CarrierAggregationParameters.CA_UL_BANDWIDTH: bandwidth_rbs,
            CarrierAggregationParameters.CA_BAND: BAND,
            CarrierAggregationParameters.CA_EARFCNDL: earfcn,
            CarrierAggregationParameters.CA_EARFCNUL: earfcn,
            CarrierAggregationParameters.CA_PCI: config.get_parameter(ParameterName.PCI) + 1,
            CarrierAggregationParameters.CA_CELL_ID: config.get_parameter(ParameterName.CELL_ID) + 1,
            CarrierAggregationParameters.CA_RADIO_ENABLE: True,
        })

    for param, value in params_to_set.items():
        config.set_parameter(param, value)
Пример #14
0
class EnodebConfigurationFactoryTest(TestCase):

    def setUp(self):
        self.cfg = EnodebConfiguration(BaicellsTrDataModel)
        self.device_cfg = EnodebConfiguration(BaicellsTrDataModel)

    def tearDown(self):
        self.cfg = None
        self.device_cfg = None

    def _get_mconfig(self):
        return {
            "@type": "type.googleapis.com/magma.mconfig.EnodebD",
            "bandwidthMhz": 20,
            "specialSubframePattern": 7,
            "earfcndl": 44490,
            "logLevel": "INFO",
            "plmnidList": "00101",
            "pci": 260,
            "allowEnodebTransmit": False,
            "subframeAssignment": 2,
            "tac": 1
        },

    def _get_service_config(self):
        return {
            "tr069": {
                "interface": "eth1",
                "port": 48080,
                "perf_mgmt_port": 8081,
                "public_ip": "192.88.99.142",
            },
            "reboot_enodeb_on_mme_disconnected": True,
            "s1_interface": "eth1",
        }

    def test_set_pci(self):
        pci = 3
        _set_pci(self.cfg, pci)
        self.assertEqual(self.cfg.get_parameter(ParameterName.PCI), pci,
                         'PCI value should be same as what was set')
        with self.assertRaises(ConfigurationError):
            _set_pci(self.cfg, 505)

    def test_set_bandwidth(self):
        mhz = 15
        _set_bandwidth(self.cfg, mhz)
        self.assertEqual(self.cfg.get_parameter(ParameterName.DL_BANDWIDTH),
                         mhz,
                         'Should have set %s' % ParameterName.DL_BANDWIDTH)
        self.assertEqual(self.cfg.get_parameter(ParameterName.UL_BANDWIDTH),
                         mhz,
                         'Should have set %s' % ParameterName.UL_BANDWIDTH)

    def test_set_tdd_subframe_config(self):
        # Not TDD mode, should not try to set anything
        self.device_cfg.set_parameter(
            ParameterName.DUPLEX_MODE_CAPABILITY, 'Not TDDMode')
        subframe = 0
        special_subframe = 0
        _set_tdd_subframe_config(self.device_cfg, self.cfg, subframe,
                                              special_subframe)
        self.assertTrue(ParameterName.SUBFRAME_ASSIGNMENT not in
                        self.cfg.get_parameter_names())

        # Invalid subframe assignment
        self.device_cfg.set_parameter(
            ParameterName.DUPLEX_MODE_CAPABILITY, 'TDDMode')
        _set_tdd_subframe_config(self.device_cfg, self.cfg, subframe,
                                 special_subframe)
        self.assertIn(ParameterName.SUBFRAME_ASSIGNMENT,
                      self.cfg.get_parameter_names(),
                      'Expected a subframe assignment')

    def test_set_management_server(self):
        _set_management_server(self.cfg)
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.PERIODIC_INFORM_ENABLE),
            True, 'Expected periodic inform enable to be true')
        self.assertTrue(
            isinstance(
                self.cfg.get_parameter(ParameterName.PERIODIC_INFORM_INTERVAL),
                int),
            'Expected periodic inform interval to ani integer'
        )

    def test_set_s1_connection(self):
        invalid_mme_ip = 1234
        invalid_mme_port = '8080'
        mme_ip = '192.168.0.1'
        mme_port = 8080

        # MME IP should be a string
        with self.assertRaises(ConfigurationError):
            _set_s1_connection(self.cfg, invalid_mme_ip, mme_port)

        # MME Port should be an integer
        with self.assertRaises(ConfigurationError):
            _set_s1_connection(self.cfg, mme_ip, invalid_mme_port)

        # Check the ip and port are sort properly
        _set_s1_connection(self.cfg, mme_ip, mme_port)
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.MME_IP), mme_ip,
            'Expected mme ip to be set')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.MME_PORT), mme_port,
            'Expected mme port to be set')

    def test_set_perf_mgmt(self):
        mgmt_ip = '192.168.0.1'
        mgmt_upload_interval = 300
        mgmt_port = 8080
        _set_perf_mgmt(self.cfg, mgmt_ip, mgmt_port)
        self.assertTrue(
            self.cfg.get_parameter(ParameterName.PERF_MGMT_ENABLE),
            'Expected perf mgmt to be enabled')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.PERF_MGMT_UPLOAD_INTERVAL),
            mgmt_upload_interval, 'Expected upload interval to be set')
        expected_url = 'http://192.168.0.1:8080/'
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.PERF_MGMT_UPLOAD_URL),
            expected_url, 'Incorrect Url')

    def test_set_misc_static_params(self):
        # IPSec enable as integer
        data_model = BaicellsTrDataModel
        self.device_cfg.set_parameter(ParameterName.IP_SEC_ENABLE, 0)
        _set_misc_static_params(self.device_cfg, self.cfg, data_model)
        self.assertTrue(
            isinstance(
                self.cfg.get_parameter(ParameterName.IP_SEC_ENABLE), int),
            'Should support an integer IP_SEC_ENABLE parameter')

        # IPSec enable as boolean
        self.device_cfg.set_parameter(ParameterName.IP_SEC_ENABLE, 'False')
        _set_misc_static_params(self.device_cfg, self.cfg, data_model)
        self.assertTrue(
            isinstance(
                self.cfg.get_parameter(ParameterName.IP_SEC_ENABLE), bool),
            'Should support a boolean IP_SEC_ENABLE parameter')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.LOCAL_GATEWAY_ENABLE), 0,
            'Should be disabled')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.CELL_RESERVED), False,
            'Should be disabled')
        self.assertEqual(
            self.cfg.get_parameter(ParameterName.MME_POOL_ENABLE), False,
            'Should be disabled')

    def test_set_plmnids_tac(self):
        # We only handle a single PLMNID for now
        data_model = BaicellsTrDataModel
        plmnids = '1, 2, 3, 4'
        tac = 1
        with self.assertRaises(ConfigurationError):
            _set_plmnids_tac(self.cfg, data_model, plmnids, tac)

        # Max PLMNID length is 6 characters
        plmnids = '1234567'
        with self.assertRaises(ConfigurationError):
            _set_plmnids_tac(self.cfg, data_model, plmnids, tac)

        # Check that only one PLMN element is enabled
        plmnids = '1'
        _set_plmnids_tac(self.cfg, data_model, plmnids, tac)
        self.assertTrue(
            self.cfg.get_parameter_for_object(
                ParameterName.PLMN_N_ENABLE % 1, ParameterName.PLMN_N % 1),
            'First PLMN should be enabled')
        self.assertFalse(self.cfg.has_object(ParameterName.PLMN_N % 2),
                         'Second PLMN should be disabled')

    def test_set_earafcn_freq_band_mode(self):
        # Invalid earfcndl
        with self.assertRaises(ConfigurationError):
            invalid_earfcndl = -1
            _set_earfcn_freq_band_mode(self.device_cfg, self.cfg,
                                       invalid_earfcndl)

        # Duplex_mode is TDD but capability is FDD
        with self.assertRaises(ConfigurationError):
            self.device_cfg.set_parameter(
                ParameterName.DUPLEX_MODE_CAPABILITY, 'FDDMode')
            earfcndl = 38650  # Corresponds to TDD
            _set_earfcn_freq_band_mode(self.device_cfg, self.cfg, earfcndl)

        # Duplex_mode is FDD but capability is TDD
        with self.assertRaises(ConfigurationError):
            self.device_cfg.set_parameter(
                ParameterName.DUPLEX_MODE_CAPABILITY, 'TDDMode')
            earfcndl = 0  # Corresponds to FDD
            _set_earfcn_freq_band_mode(self.device_cfg, self.cfg, earfcndl)
Пример #15
0
class BasicEnodebAcsStateMachine(EnodebAcsStateMachine):
    """
    Most of the EnodebAcsStateMachine classes for each device work about the
    same way. Differences lie mainly in the data model, desired configuration,
    and the state transition map.

    This class specifies the shared implementation between them.
    """

    # eNodeB connection timeout is used to determine whether or not eNodeB is
    # connected to enodebd based on time of last Inform message. By default,
    # periodic inform interval is 30secs, so timeout should be larger than
    # this.
    # Also set timer longer than reboot time, so that an eNodeB reboot does not
    # trigger a connection-timeout alarm.
    ENB_CONNECTION_TIMEOUT = 600  # In seconds

    # If eNodeB is disconnected from MME for an unknown reason for this time,
    # then reboot it. Set to a long time to ensure this doesn't interfere with
    # other enodebd configuration processes - it is just a measure of last
    # resort for an unlikely error case
    MME_DISCONNECT_ENODEB_REBOOT_TIMER = 15 * 60

    # Check the MME connection status every 15 seconds
    MME_CHECK_TIMER = 15

    def __init__(
        self,
        service: MagmaService,
    ) -> None:
        super().__init__()
        self.state = None
        self.timeout_handler = None
        self.mme_timeout_handler = None
        self.mme_timer = None
        self._start_state_machine(service)

    def get_state(self) -> str:
        if self.state is None:
            logger.warning('ACS State machine is not in any state.')
            return 'N/A'
        return self.state.state_description()

    def handle_tr069_message(
        self,
        message: Tr069ComplexModel,
    ) -> Tr069ComplexModel:
        """
        Accept the tr069 message from the eNB and produce a reply.

        States may transition after reading a message but BEFORE producing
        a reply. Most steps in the provisioning process are represented as
        beginning with enodebd sending a request to the eNB, and waiting for
        the reply from the eNB.
        """
        # TransferComplete messages come at random times, and we ignore them
        if isinstance(message, models.TransferComplete):
            return models.TransferCompleteResponse()
        try:
            self._read_tr069_msg(message)
            return self._get_tr069_msg(message)
        except Exception:  # pylint: disable=broad-except
            logger.error('Failed to handle tr069 message')
            logger.error(traceback.format_exc())
            self._dump_debug_info()
            self.transition(self.unexpected_fault_state_name)
            return self._get_tr069_msg(message)

    def transition(self, next_state: str) -> Any:
        logger.debug('State transition to <%s>', next_state)
        self.state.exit()
        self.state = self.state_map[next_state]
        self.state.enter()

    def stop_state_machine(self) -> None:
        """ Clean up anything the state machine is tracking or doing """
        self.state.exit()
        if self.timeout_handler is not None:
            self.timeout_handler.cancel()
            self.timeout_handler = None
        if self.mme_timeout_handler is not None:
            self.mme_timeout_handler.cancel()
            self.mme_timeout_handler = None
        self._service = None
        self._desired_cfg = None
        self._device_cfg = None
        self._data_model = None

        self.mme_timer = None

    def _start_state_machine(
        self,
        service: MagmaService,
    ):
        self.service = service
        self.data_model = self.data_model_class()
        # The current known device config has few known parameters
        # The desired configuration depends on what the current configuration
        # is. This we don't know fully, yet.
        self.device_cfg = EnodebConfiguration(self.data_model)

        self._init_state_map()
        self.state = self.state_map[self.disconnected_state_name]
        self.state.enter()
        self._reset_timeout()
        self._periodic_check_mme_connection()

    def _reset_state_machine(
        self,
        service: MagmaService,
    ):
        self.stop_state_machine()
        self._start_state_machine(service)

    def _read_tr069_msg(self, message: Any) -> None:
        """ Process incoming message and maybe transition state """
        self._reset_timeout()
        msg_handled, next_state = self.state.read_msg(message)
        if not msg_handled:
            self._transition_for_unexpected_msg(message)
            _msg_handled, next_state = self.state.read_msg(message)
        if next_state is not None:
            self.transition(next_state)

    def _get_tr069_msg(self, message: Any) -> Any:
        """ Get a new message to send, and maybe transition state """
        msg_and_transition = self.state.get_msg(message)
        if msg_and_transition.next_state:
            self.transition(msg_and_transition.next_state)
        msg = msg_and_transition.msg
        return msg

    def _transition_for_unexpected_msg(self, message: Any) -> None:
        """
        eNB devices may send an Inform message in the middle of a provisioning
        session. To deal with this, transition to a state that expects an
        Inform message, but also track the status of the eNB as not having
        been disconnected.
        """
        if isinstance(message, models.Inform):
            logger.debug(
                'ACS in (%s) state. Received an Inform message',
                self.state.state_description(),
            )
            self._reset_state_machine(self.service)
        elif isinstance(message, models.Fault):
            logger.debug(
                'ACS in (%s) state. Received a Fault <%s>',
                self.state.state_description(),
                message.FaultString,
            )
            self.transition(self.unexpected_fault_state_name)
        else:
            raise ConfigurationError('Cannot handle unexpected TR069 msg')

    def _reset_timeout(self) -> None:
        if self.timeout_handler is not None:
            self.timeout_handler.cancel()

        def timed_out():
            self.transition(self.disconnected_state_name)

        self.timeout_handler = self.event_loop.call_later(
            self.ENB_CONNECTION_TIMEOUT,
            timed_out,
        )

    def _periodic_check_mme_connection(self) -> None:
        self._check_mme_connection()
        self.mme_timeout_handler = self.event_loop.call_later(
            self.MME_CHECK_TIMER,
            self._periodic_check_mme_connection,
        )

    def _check_mme_connection(self) -> None:
        """
        Check if eNodeB should be connected to MME but isn't, and maybe reboot.

        If the eNB doesn't report connection to MME within a timeout period,
        get it to reboot in the hope that it will fix things.

        Usually, enodebd polls the eNodeB for whether it is connected to MME.
        This method checks the last polled MME connection status, and if
        eNodeB should be connected to MME but it isn't.
        """
        if self.device_cfg.has_parameter(ParameterName.MME_STATUS) and \
                self.device_cfg.get_parameter(ParameterName.MME_STATUS):
            is_mme_connected = 1
        else:
            is_mme_connected = 0

        # True if we would expect MME to be connected, but it isn't
        is_mme_unexpectedly_dc = \
            self.is_enodeb_connected() \
            and self.is_enodeb_configured() \
            and self.mconfig.allow_enodeb_transmit \
            and not is_mme_connected

        if is_mme_unexpectedly_dc:
            logger.warning(
                'eNodeB is connected to AGw, is configured, '
                'and has AdminState enabled for transmit. '
                'MME connection to eNB is missing.', )
            if self.mme_timer is None:
                logger.warning(
                    'eNodeB will be rebooted if MME connection '
                    'is not established in: %s seconds.',
                    self.MME_DISCONNECT_ENODEB_REBOOT_TIMER,
                )
                metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(1)
                self.mme_timer = \
                    StateMachineTimer(self.MME_DISCONNECT_ENODEB_REBOOT_TIMER)
            elif self.mme_timer.is_done():
                logger.warning(
                    'eNodeB has not established MME connection '
                    'within %s seconds - rebooting!',
                    self.MME_DISCONNECT_ENODEB_REBOOT_TIMER,
                )
                metrics.STAT_ENODEB_REBOOTS.labels(
                    cause='MME disconnect').inc()
                metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(0)
                self.mme_timer = None
                self.reboot_asap()
            else:
                # eNB is not connected to MME, but we're still waiting to see
                # if it will connect within the timeout period.
                # Take no action for now.
                pass
        else:
            if self.mme_timer is not None:
                logger.info('eNodeB has established MME connection.')
                self.mme_timer = None
            metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(0)

    def _dump_debug_info(self) -> None:
        if self.device_cfg is not None:
            logger.error(
                'Device configuration: %s',
                self.device_cfg.get_debug_info(),
            )
        else:
            logger.error('Device configuration: None')
        if self.desired_cfg is not None:
            logger.error(
                'Desired configuration: %s',
                self.desired_cfg.get_debug_info(),
            )
        else:
            logger.error('Desired configuration: None')

    @abstractmethod
    def _init_state_map(self) -> None:
        pass

    @property
    @abstractmethod
    def state_map(self) -> Dict[str, EnodebAcsState]:
        pass

    @property
    @abstractmethod
    def disconnected_state_name(self) -> str:
        pass

    @property
    @abstractmethod
    def unexpected_fault_state_name(self) -> str:
        """ State to handle unexpected Fault messages """
        pass
Пример #16
0
def _get_enb_config(
    mconfig: mconfigs_pb2.EnodebD,
    device_config: EnodebConfiguration,
) -> SingleEnodebConfig:
    # For fields that are specified per eNB
    if mconfig.enb_configs_by_serial is not None and \
            len(mconfig.enb_configs_by_serial) > 0:
        enb_serial = \
            device_config.get_parameter(ParameterName.SERIAL_NUMBER)
        if enb_serial in mconfig.enb_configs_by_serial:
            enb_config = mconfig.enb_configs_by_serial[enb_serial]
            earfcndl = enb_config.earfcndl
            pci = enb_config.pci
            allow_enodeb_transmit = enb_config.transmit_enabled
            tac = enb_config.tac
            bandwidth_mhz = enb_config.bandwidth_mhz
            cell_id = enb_config.cell_id
            duplex_mode = map_earfcndl_to_duplex_mode(earfcndl)
            subframe_assignment = None
            special_subframe_pattern = None
            if duplex_mode == DuplexMode.TDD:
                subframe_assignment = enb_config.subframe_assignment
                special_subframe_pattern = \
                    enb_config.special_subframe_pattern
        else:
            raise ConfigurationError('Could not construct desired config '
                                     'for eNB')
    else:
        pci = mconfig.pci
        allow_enodeb_transmit = mconfig.allow_enodeb_transmit
        tac = mconfig.tac
        bandwidth_mhz = mconfig.bandwidth_mhz
        cell_id = DEFAULT_CELL_IDENTITY
        if mconfig.tdd_config is not None and str(mconfig.tdd_config) != '':
            earfcndl = mconfig.tdd_config.earfcndl
            subframe_assignment = mconfig.tdd_config.subframe_assignment
            special_subframe_pattern = \
                mconfig.tdd_config.special_subframe_pattern
        elif mconfig.fdd_config is not None and str(mconfig.fdd_config) != '':
            earfcndl = mconfig.fdd_config.earfcndl
            subframe_assignment = None
            special_subframe_pattern = None
        else:
            earfcndl = mconfig.earfcndl
            subframe_assignment = mconfig.subframe_assignment
            special_subframe_pattern = mconfig.special_subframe_pattern

    # And now the rest of the fields
    plmnid_list = mconfig.plmnid_list

    single_enodeb_config = SingleEnodebConfig(
        earfcndl=earfcndl,
        subframe_assignment=subframe_assignment,
        special_subframe_pattern=special_subframe_pattern,
        pci=pci,
        plmnid_list=plmnid_list,
        tac=tac,
        bandwidth_mhz=bandwidth_mhz,
        cell_id=cell_id,
        allow_enodeb_transmit=allow_enodeb_transmit,
        mme_address=None,
        mme_port=None)
    return single_enodeb_config
Пример #17
0
    def set_magma_device_cfg(
        cls,
        name_to_val: Dict,
        device_cfg: EnodebConfiguration,
    ):
        """
        Convert FreedomFiOne name_to_val representation to magma device_cfg
        """
        success_str = "SUCCESS"  # String constant returned by radio
        insync_str = "INSYNC"

        if (name_to_val.get(cls.DEFAULT_GW)
                and name_to_val[cls.DEFAULT_GW].upper() != success_str):
            # Nothing will proceed if the eNB doesn't have an IP on the WAN
            serial_num = "unknown"
            if device_cfg.has_parameter(ParameterName.SERIAL_NUMBER):
                serial_num = device_cfg.get_parameter(
                    ParameterName.SERIAL_NUMBER, )
            EnodebdLogger.error(
                "Radio with serial number %s doesn't have IP address "
                "on WAN",
                serial_num,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.RF_TX_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.GPS_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.PTP_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.MME_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.OP_STATE,
                value=False,
            )
            return

        if (name_to_val.get(cls.SAS_STATUS)
                and name_to_val[cls.SAS_STATUS].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.RF_TX_STATUS,
                value=True,
            )
        else:
            # No sas grant so not transmitting. There is no explicit node for Tx
            # in FreedomFiOne
            device_cfg.set_parameter(
                param_name=ParameterName.RF_TX_STATUS,
                value=False,
            )

        if (name_to_val.get(cls.GPS_SCAN_STATUS)
                and name_to_val[cls.GPS_SCAN_STATUS].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.GPS_STATUS,
                value=True,
            )
            # Time comes through GPS so can only be insync with GPS is
            # in sync, we use PTP_STATUS field to overload timer is in Sync.
            if (name_to_val.get(cls.SYNC_STATUS)
                    and name_to_val[cls.SYNC_STATUS].upper() == insync_str):
                device_cfg.set_parameter(
                    param_name=ParameterName.PTP_STATUS,
                    value=True,
                )
            else:
                device_cfg.set_parameter(
                    param_name=ParameterName.PTP_STATUS,
                    value=False,
                )
        else:
            device_cfg.set_parameter(
                param_name=ParameterName.GPS_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.PTP_STATUS,
                value=False,
            )

        if (name_to_val.get(cls.DEFAULT_GW)
                and name_to_val[cls.DEFAULT_GW].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.MME_STATUS,
                value=True,
            )
        else:
            device_cfg.set_parameter(
                param_name=ParameterName.MME_STATUS,
                value=False,
            )

        if (name_to_val.get(cls.ENB_STATUS)
                and name_to_val[cls.ENB_STATUS].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.OP_STATE,
                value=True,
            )
        else:
            device_cfg.set_parameter(
                param_name=ParameterName.OP_STATE,
                value=False,
            )

        pass_through_params = [ParameterName.GPS_LAT, ParameterName.GPS_LONG]
        for name in pass_through_params:
            device_cfg.set_parameter(name, name_to_val[name])
Пример #18
0
def _set_earfcn_freq_band_mode(
    device_cfg: EnodebConfiguration,
    cfg: EnodebConfiguration,
    data_model: DataModel,
    earfcndl: int,
) -> None:
    """
    Set the following parameters:
     - EARFCNDL
     - EARFCNUL
     - Band
    """
    # Note: validation of EARFCNDL done by mapping function. If invalid
    # EARFCN, raise ConfigurationError
    try:
        band, duplex_mode, earfcnul = map_earfcndl_to_band_earfcnul_mode(
            earfcndl)
    except ValueError as err:
        raise ConfigurationError(err)

    # Verify capabilities
    duplex_capability =\
        device_cfg.get_parameter(ParameterName.DUPLEX_MODE_CAPABILITY)
    if duplex_mode == DuplexMode.TDD and duplex_capability != 'TDDMode':
        raise ConfigurationError(
            ('eNodeB duplex mode capability is <{0}>, '
             'but earfcndl is <{1}>, giving duplex '
             'mode <{2}> instead').format(duplex_capability, str(earfcndl),
                                          str(duplex_mode)))
    elif duplex_mode == DuplexMode.FDD and duplex_capability != 'FDDMode':
        raise ConfigurationError(
            ('eNodeB duplex mode capability is <{0}>, '
             'but earfcndl is <{1}>, giving duplex '
             'mode <{2}> instead').format(duplex_capability, str(earfcndl),
                                          str(duplex_mode)))
    elif duplex_mode not in [DuplexMode.TDD, DuplexMode.FDD]:
        raise ConfigurationError('Invalid duplex mode (%s)' % str(duplex_mode))

    # Baicells indicated that they no longer use the band capability list,
    # so it may not be populated correctly
    band_capability_list = device_cfg.get_parameter(
        ParameterName.BAND_CAPABILITY)
    band_capabilities = band_capability_list.split(',')
    if str(band) not in band_capabilities:
        logging.warning(
            'Band %d not in capabilities list (%s). Continuing'
            ' with config because capabilities list may not be'
            ' correct', band, band_capabilities)

    cfg.set_parameter(ParameterName.EARFCNDL, earfcndl)
    if duplex_mode == DuplexMode.FDD:
        cfg.set_parameter(ParameterName.EARFCNUL, earfcnul)
    else:
        logging.debug('Not setting EARFCNUL - duplex mode is not FDD')

    _set_param_if_present(cfg, data_model, ParameterName.BAND, band)

    if duplex_mode == DuplexMode.TDD:
        logging.debug('Set EARFCNDL=%d, Band=%d', earfcndl, band)
    else:
        logging.debug('Set EARFCNDL=%d, EARFCNUL=%d, Band=%d', earfcndl,
                      earfcnul, band)