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, )
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
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)
def process_inform_message( inform: Any, data_model: DataModel, device_cfg: EnodebConfiguration, ) -> None: """ Modifies the device configuration based on what is received in the Inform message. Will raise an error if it turns out that the data model we are using is incorrect. This is decided based on the device OUI and software-version that is reported in the Inform message. Args: inform: Inform Tr069 message device_handler: The state machine we are using for our device """ param_values_by_path = _get_param_values_by_path(inform) param_name_list = data_model.get_parameter_names() name_to_val = {} for name in param_name_list: path = data_model.get_parameter(name).path if path in param_values_by_path: value = param_values_by_path[path] name_to_val[name] = value for name, val in name_to_val.items(): device_cfg.set_parameter(name, val)
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
def process_inform_message( inform: Any, data_model: DataModel, device_cfg: EnodebConfiguration, ) -> None: """ Modifies the device configuration based on what is received in the Inform message. Will raise an error if it turns out that the data model we are using is incorrect. This is decided based on the device OUI and software-version that is reported in the Inform message. Args: inform: Inform Tr069 message device_handler: The state machine we are using for our device """ param_values_by_path = _get_param_values_by_path(inform) param_name_list = data_model.get_parameter_names() name_to_val = {} for name in param_name_list: path = data_model.get_parameter(name).path if path in param_values_by_path: value = param_values_by_path[path] name_to_val[name] = value for name, val in name_to_val.items(): device_cfg.set_parameter(name, val) # In case the SerialNumber does not come in the inform ParameterList # it can still be present in the Inform structure, fill it in. if (hasattr(inform, 'DeviceId') and hasattr(inform.DeviceId, 'SerialNumber')): device_cfg.set_parameter(ParameterName.SERIAL_NUMBER, inform.DeviceId.SerialNumber)
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
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 sas grant") config.set_parameter(ParameterName.SAS_RADIO_ENABLE, state.radio_enabled) if not state.radio_enabled: return earfcn = calc_earfcn(state.channel.low_frequency_hz, state.channel.high_frequency_hz) bandwidth_mhz = calc_bandwidth_mhz(state.channel.low_frequency_hz, state.channel.high_frequency_hz) bandwidth_rbs = calc_bandwidth_rbs(bandwidth_mhz) psd = _calc_psd(state.channel.max_eirp_dbm_mhz) 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, } for param, value in params_to_set.items(): config.set_parameter(param, value)
def _set_cell_id( cfg: EnodebConfiguration, cell_id: int, ) -> None: config_assert(cell_id in range(0, 268435456), 'Cell Identity should be from 0 - (2^28 - 1)') cfg.set_parameter(ParameterName.CELL_ID, cell_id)
def _set_param_if_present( cfg: EnodebConfiguration, data_model: DataModel, param: ParameterName, value: Any, ) -> None: if data_model.is_parameter_present(param): cfg.set_parameter(param, value)
def build_desired_config( mconfig: Any, service_config: Any, device_config: EnodebConfiguration, data_model: DataModel, post_processor: EnodebConfigurationPostProcessor, ) -> EnodebConfiguration: """ Factory for initializing DESIRED data model configuration. When working with the configuration of an eNodeB, we track the current state of configuration for that device, as well as what configuration we want to set on the device. Args: mconfig: Managed configuration, eNodeB protobuf message service_config: Returns: Desired data model configuration for the device """ cfg_desired = EnodebConfiguration(data_model) # Determine configuration parameters _set_management_server(cfg_desired) # Attempt to load device configuration from YANG before service mconfig enb_config = _get_enb_yang_config(device_config) or \ _get_enb_config(mconfig, device_config) _set_earfcn_freq_band_mode(device_config, cfg_desired, data_model, enb_config.earfcndl) if enb_config.subframe_assignment is not None: _set_tdd_subframe_config(device_config, cfg_desired, enb_config.subframe_assignment, enb_config.special_subframe_pattern) _set_pci(cfg_desired, enb_config.pci) _set_plmnids_tac(cfg_desired, enb_config.plmnid_list, enb_config.tac) _set_bandwidth(cfg_desired, data_model, enb_config.bandwidth_mhz) _set_cell_id(cfg_desired, enb_config.cell_id) _set_perf_mgmt( cfg_desired, get_ip_from_if(service_config['tr069']['interface']), service_config['tr069']['perf_mgmt_port']) _set_misc_static_params(device_config, cfg_desired, data_model) if enb_config.mme_address is not None and enb_config.mme_port is not None: _set_s1_connection(cfg_desired, enb_config.mme_address, enb_config.mme_port) else: _set_s1_connection( cfg_desired, get_ip_from_if(service_config['s1_interface'])) # Enable LTE if we should cfg_desired.set_parameter(ParameterName.ADMIN_STATE, enb_config.allow_enodeb_transmit) post_processor.postprocess(cfg_desired) return cfg_desired
def _set_management_server(cfg: EnodebConfiguration) -> None: """ Set the following parameters: - Periodic inform enable - Periodic inform interval (hard-coded) """ cfg.set_parameter(ParameterName.PERIODIC_INFORM_ENABLE, True) # In seconds cfg.set_parameter(ParameterName.PERIODIC_INFORM_INTERVAL, 5)
def build_desired_config( mconfig: Any, service_config: Any, device_config: EnodebConfiguration, data_model: DataModel, post_processor: EnodebConfigurationPostProcessor, ) -> EnodebConfiguration: """ Factory for initializing DESIRED data model configuration. When working with the configuration of an eNodeB, we track the current state of configuration for that device, as well as what configuration we want to set on the device. Args: mconfig: Managed configuration, eNodeB protobuf message service_config: Returns: Desired data model configuration for the device """ cfg_desired = EnodebConfiguration(data_model) # Determine configuration parameters _set_management_server(cfg_desired) if mconfig.tdd_config is not None and str(mconfig.tdd_config) != '': _set_earfcn_freq_band_mode(device_config, cfg_desired, data_model, mconfig.tdd_config.earfcndl) _set_tdd_subframe_config(device_config, cfg_desired, mconfig.tdd_config.subframe_assignment, mconfig.tdd_config.special_subframe_pattern) elif mconfig.fdd_config is not None and str(mconfig.fdd_config) != '': _set_earfcn_freq_band_mode(device_config, cfg_desired, data_model, mconfig.fdd_config.earfcndl) else: # back-compat: use legacy fields if tdd/fdd aren't set _set_earfcn_freq_band_mode(device_config, cfg_desired, data_model, mconfig.earfcndl) _set_tdd_subframe_config(device_config, cfg_desired, mconfig.subframe_assignment, mconfig.special_subframe_pattern) _set_pci(cfg_desired, mconfig.pci) _set_plmnids_tac(cfg_desired, mconfig.plmnid_list, mconfig.tac) _set_bandwidth(cfg_desired, data_model, mconfig.bandwidth_mhz) _set_s1_connection(cfg_desired, get_ip_from_if(service_config['s1_interface'])) _set_perf_mgmt(cfg_desired, get_ip_from_if(service_config['tr069']['interface']), service_config['tr069']['perf_mgmt_port']) _set_misc_static_params(device_config, cfg_desired, data_model) # Enable LTE if we should cfg_desired.set_parameter(ParameterName.ADMIN_STATE, mconfig.allow_enodeb_transmit) post_processor.postprocess(cfg_desired) return cfg_desired
def _set_pci( cfg: EnodebConfiguration, pci: Any, ) -> None: """ Set the following parameters: - PCI """ if pci not in range(0, 504 + 1): raise ConfigurationError('Invalid PCI (%d)' % pci) cfg.set_parameter(ParameterName.PCI, pci)
def get_all_objects_to_delete( desired_cfg: EnodebConfiguration, device_cfg: EnodebConfiguration, ) -> List[ParameterName]: """ Find a ParameterName that needs to be deleted from the eNB configuration, if any """ desired = desired_cfg.get_object_names() current = device_cfg.get_object_names() return list(set(current).difference(set(desired)))
def _set_bandwidth( cfg: EnodebConfiguration, data_model: DataModel, bandwidth_mhz: Any, ) -> None: """ Set the following parameters: - DL bandwidth - UL bandwidth """ cfg.set_parameter(ParameterName.DL_BANDWIDTH, bandwidth_mhz) _set_param_if_present(cfg, data_model, ParameterName.UL_BANDWIDTH, bandwidth_mhz)
def _set_s1_connection( cfg: EnodebConfiguration, mme_ip: Any, mme_port: Any = DEFAULT_S1_PORT, ) -> None: """ Set the following parameters: - MME IP - MME port (defalts to 36412 as per TR-196 recommendation) """ config_assert(type(mme_ip) == str, 'Invalid MME IP type') config_assert(type(mme_port) == int, 'Invalid MME Port type') cfg.set_parameter(ParameterName.MME_IP, mme_ip) cfg.set_parameter(ParameterName.MME_PORT, mme_port)
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, )
def test_tx_parameters_with_eirp_within_range( self, low_frequency_hz, high_frequency_hz, max_eirp_dbm_mhz, expected_bw_rbs, expected_earfcn, expected_tx_power, ) -> None: """Test that tx parameters of the enodeb are calculated correctly when eirp received from SAS is within acceptable range for the given bandwidth""" desired_config = EnodebConfiguration(FreedomFiOneTrDataModel()) channel = LteChannel( low_frequency_hz=low_frequency_hz, high_frequency_hz=high_frequency_hz, max_eirp_dbm_mhz=max_eirp_dbm_mhz, ) state = CBSDStateResult( radio_enabled=True, channel=channel, ) ff_one_update_desired_config_from_cbsd_state(state, desired_config) self.assert_config_updated( config=desired_config, bandwidth=expected_bw_rbs, earfcn=expected_earfcn, tx_power=expected_tx_power, radio_enabled=True, )
def read_msg(self, message: Any) -> Optional[str]: """ In this case, we assume that we already know things such as the manufacturer OUI, and also the SW-version of the eNodeB which is sending the Inform message, so we don't process the message. We just check that we're getting the right message type that we expect. Returns: InformResponse """ if type(message) == models.Fault: raise Tr069Error( 'ACS in WaitInform state. Received a Fault message. ' '(faultstring = %s)' % message.FaultString) elif not isinstance(message, models.Inform): raise ConfigurationError( 'ACS in WaitInform state. Expected an Inform message. ' + 'Received a %s message.' % type(message)) is_correct_event = False for event in message.Event.EventStruct: logging.debug('Inform event: %s', event.EventCode) if event.EventCode == self.INFORM_EVENT_CODE: # Mark eNodeB as unconfigured, since some config params # are reset on reboot (e.g. perf mgmt enable) logging.info('eNodeB booting - reconfig required') self.acs.device_cfg = EnodebConfiguration(self.acs.data_model) is_correct_event = True if not is_correct_event: raise Tr069Error('Did not receive 1 BOOT event code in ' 'Inform') process_inform_message(message, self.acs.device_name, self.acs.data_model, self.acs.device_cfg) return 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 get_all_objects_to_add( desired_cfg: EnodebConfiguration, device_cfg: EnodebConfiguration, ) -> List[ParameterName]: """ Find a ParameterName that needs to be added to the eNB configuration, if any Note: This is the expected name of the parameter once it is added but this is different than how to add it. For example, enumerated objects of the form XX.YY.N. should be added by calling AddObject to XX.YY. and having the CPE assign the index. """ desired = desired_cfg.get_object_names() current = device_cfg.get_object_names() return list(set(desired).difference(set(current)))
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))
def provision_clean_sm(state=None): acs_state_machine = EnodebAcsStateMachineBuilder.build_acs_state_machine( EnodebDeviceName.BAICELLS_QRTB) acs_state_machine.desired_cfg = EnodebConfiguration( BaicellsQRTBTrDataModel(), ) if state is not None: acs_state_machine.transition(state) return acs_state_machine
def postprocess(self, mconfig: Any, service_cfg: Any, desired_cfg: EnodebConfiguration) -> None: """ Add some params to the desired config Args: mconfig (Any): mconfig service_cfg (Any): service config desired_cfg (EnodebConfiguration): desired config """ desired_cfg.set_parameter(ParameterName.SAS_ENABLED, 1) desired_cfg.set_parameter_for_object( ParameterName.PLMN_N_CELL_RESERVED % 1, True, # noqa: WPS345,WPS425 ParameterName.PLMN_N % 1, # noqa: WPS345 ) parameters_to_delete = [ ParameterName.RADIO_ENABLE, ParameterName.POWER_SPECTRAL_DENSITY, ParameterName.EARFCNDL, ParameterName.EARFCNUL, ParameterName.BAND, ParameterName.DL_BANDWIDTH, ParameterName.UL_BANDWIDTH, ParameterName.SAS_RADIO_ENABLE, ] for p in parameters_to_delete: if desired_cfg.has_parameter(p): desired_cfg.delete_parameter(p)
def postprocess( self, mconfig: Any, service_cfg: Any, desired_cfg: EnodebConfiguration, ) -> None: # TODO: Get this config from the domain proxy # TODO @amarpad, set these when DProxy integration is done. # For now the radio will directly talk to the SAS and get these # attributes. desired_cfg.delete_parameter(ParameterName.EARFCNDL) desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH) desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH) # go through misc parameters and set them to default. for name, val in FreedomFiOneMiscParameters.defaults.items(): desired_cfg.set_parameter(name, val) # Bump up the parameter key version self.acs.parameter_version_inc() if self.WEB_UI_ENABLE_LIST_KEY in service_cfg: serial_nos = service_cfg.get(self.WEB_UI_ENABLE_LIST_KEY) if self.acs.device_cfg.has_parameter( ParameterName.SERIAL_NUMBER, ): if self.acs.get_parameter(ParameterName.SERIAL_NUMBER) in \ serial_nos: desired_cfg.set_parameter( FreedomFiOneMiscParameters.WEB_UI_ENABLE, True, ) else: # This should not happen EnodebdLogger.error("Serial number unknown for device") if self.SAS_KEY not in service_cfg: return sas_cfg = service_cfg[self.SAS_KEY] sas_param_names = self.acs.data_model.get_sas_param_names() for name, val in sas_cfg.items(): if name not in sas_param_names: EnodebdLogger.warning("Ignoring attribute %s", name) continue desired_cfg.set_parameter(name, val)
def get_obj_param_values_to_set( desired_cfg: EnodebConfiguration, device_cfg: EnodebConfiguration, data_model: DataModel, ) -> Dict[ParameterName, Dict[ParameterName, Any]]: """ Returns a map from object name to (a map of param name to value) """ param_values = {} objs = desired_cfg.get_object_names() for obj_name in objs: param_values[obj_name] = {} params = desired_cfg.get_parameter_names_for_object(obj_name) for name in params: new = desired_cfg.get_parameter_for_object(name, obj_name) old = device_cfg.get_parameter_for_object(name, obj_name) _type = data_model.get_parameter(name).type if not are_tr069_params_equal(new, old, _type): param_values[obj_name][name] = new return param_values
def get_params_to_get( device_cfg: EnodebConfiguration, data_model: DataModel, ) -> List[ParameterName]: """ Returns the names of params not belonging to objects that are added/removed """ desired_names = data_model.get_present_params() known_names = device_cfg.get_parameter_names() names = list(set(desired_names) - set(known_names)) return names
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)
def test_power_spectral_density_above_range(self) -> None: config = EnodebConfiguration(BaicellsQRTBTrDataModel()) channel = LteChannel( low_frequency_hz=3550_000_000, high_frequency_hz=3560_000_000, max_eirp_dbm_mhz=38, ) state = CBSDStateResult( radio_enabled=True, channel=channel, ) with self.assertRaises(ConfigurationError): qrtb_update_desired_config_from_cbsd_state(state, config)