def _build_cbsd(self, session: Session, cbsd: DBCbsd) -> Cbsd: db_grants = session.query(DBGrant).join(DBGrantState).filter( DBGrant.cbsd_id == cbsd.id, DBGrantState.name != GrantStates.IDLE.value, ) pending_requests_payloads = session.query(DBRequest.payload, ).join( DBRequestState, ).filter( DBRequestState.name == RequestStates.PENDING.value, DBRequest.cbsd_id == cbsd.id, ) grants = [self._build_grant(x) for x in db_grants] channels = [self._build_channel(x) for x in cbsd.channels] pending_requests = [ json.dumps(r.payload, separators=(',', ':')) for r in pending_requests_payloads ] last_seen = self._to_timestamp(cbsd.last_seen) eirp_capabilities = self._build_eirp_capabilities(cbsd) return Cbsd( id=cbsd.cbsd_id, user_id=cbsd.user_id, fcc_id=cbsd.fcc_id, serial_number=cbsd.cbsd_serial_number, state=cbsd_state_mapping[cbsd.state.name], grants=grants, channels=channels, pending_requests=pending_requests, last_seen_timestamp=last_seen, eirp_capabilities=eirp_capabilities, )
def _get_or_create_grant_from_response( obj: ResponseDBProcessor, response: DBResponse, session: Session, ) -> Optional[DBGrant]: cbsd_id = response.payload.get( CBSD_ID, ) or response.request.payload.get(CBSD_ID) grant_id = response.payload.get( GRANT_ID, ) or response.request.payload.get(GRANT_ID) cbsd = session.query(DBCbsd).filter(DBCbsd.cbsd_id == cbsd_id).scalar() grant = None if grant_id: logger.info(f'Getting grant by: {cbsd_id=} {grant_id=}') grant = session.query(DBGrant).filter( DBGrant.cbsd_id == cbsd.id, DBGrant.grant_id == grant_id, ).scalar() if grant_id and not grant: grant_idle_state = obj.grant_states_map[GrantStates.IDLE.value] grant = DBGrant(cbsd=cbsd, grant_id=grant_id, state=grant_idle_state) _update_grant_from_request(response, grant) session.add(grant) logger.info(f'Created new grant: {grant}') return grant
def _build_state(self, session: Session) -> State: db_grant_idle_state_id = session.query(DBGrantState.id).filter( DBGrantState.name == GrantStates.IDLE.value, ).scalar() db_request_pending_state_id = session.query(DBRequestState.id).filter( DBRequestState.name == RequestStates.PENDING.value, ).scalar() # Selectively load sqlalchemy object relations using a single query to avoid commit races. # We want to have CBSD entity "grants" relation only contain grants in a Non-IDLE state. # We want to have CBSD entity "requests" relation only contain PENDING requests. db_configs = session.query(DBActiveModeConfig).join(DBCbsd).options( joinedload(DBActiveModeConfig.cbsd).options( joinedload(DBCbsd.channels), joinedload( DBCbsd.grants.and_( DBGrant.state_id != db_grant_idle_state_id, ), ), joinedload( DBCbsd.requests.and_( DBRequest.state_id == db_request_pending_state_id, ), ), ), ).filter(*self._get_filter()).populate_existing() configs = [self._build_config(db_config) for db_config in db_configs] session.commit() return State(active_mode_configs=configs)
def _find_cbsd_from_request(session: Session, payload: Dict) -> DBCbsd: if "cbsdSerialNumber" in payload: return session.query(DBCbsd).filter( DBCbsd.cbsd_serial_number == payload.get("cbsdSerialNumber"), ).scalar() if "cbsdId" in payload: return session.query(DBCbsd).filter( DBCbsd.cbsd_id == payload.get("cbsdId"), ).scalar() logger.warning(f'Could not find CBSD in Database matching {payload=}.')
def _get_authorized_grant(self, session: Session, cbsd: DBCbsd) -> DBGrant: authorized_state = session.query(DBGrantState). \ filter(DBGrantState.name == GrantStates.AUTHORIZED.value).first() grant = session.query(DBGrant).filter( DBGrant.cbsd_id == cbsd.id, DBGrant.state_id == authorized_state.id, (DBGrant.transmit_expire_time == None) | ( # noqa: WPS465,E711 DBGrant.transmit_expire_time > now()), (DBGrant.grant_expire_time == None) | ( # noqa: WPS465,E711 DBGrant.grant_expire_time > now()), ).first() return grant
def _terminate_all_grants_from_response(response: DBResponse, session: Session) -> None: cbsd_id = response.payload.get( CBSD_ID, ) or response.request.payload.get(CBSD_ID) if not cbsd_id: return cbsd = session.query(DBCbsd).filter(DBCbsd.cbsd_id == cbsd_id).scalar() if cbsd: cbsd.grant_attempts = 0 logger.info(f'Terminating all grants for {cbsd_id=}') session.query(DBGrant).filter(DBGrant.cbsd == cbsd).delete() logger.info(f"Deleting all channels for {cbsd_id=}") session.query(DBChannel).filter(DBChannel.cbsd == cbsd).delete()
def _create_or_update_active_mode_config(self, session: Session, cbsd: DBCbsd) -> DBActiveModeConfig: registered_state = session.query(DBCbsdState). \ filter(DBCbsdState.name == CbsdStates.REGISTERED.value).first() active_mode_config = session.query(DBActiveModeConfig). \ filter(DBActiveModeConfig.cbsd_id == cbsd.id).first() if active_mode_config: active_mode_config.desired_state = registered_state return None active_mode_config = DBActiveModeConfig( desired_state=registered_state, cbsd=cbsd, ) session.add(active_mode_config) return active_mode_config
def _terminate_all_grants_from_response(response: DBResponse, session: Session) -> None: cbsd_id = response.payload.get( CBSD_ID, ) or response.request.payload.get(CBSD_ID) if not cbsd_id: return logger.info(f'Terminating all grants for {cbsd_id=}') with session.no_autoflush: session.query(DBGrant). \ filter(DBGrant.cbsd_id == DBCbsd.id, DBCbsd.cbsd_id == cbsd_id). \ delete(synchronize_session=False) logger.info(f"Deleting all channels for {cbsd_id=}") session.query(DBChannel). \ filter(DBChannel.cbsd_id == DBCbsd.id, DBCbsd.cbsd_id == cbsd_id). \ delete(synchronize_session=False)
def _get_or_create_cbsd(self, session: Session, request_type: str, request_json: Dict) -> Optional[DBCbsd]: filters = self._get_cbsd_filters(request_type, request_json) cbsd = session.query(DBCbsd).filter(*filters).first() cbsd_id = request_json.get(CBSD_ID) return cbsd if cbsd else self._create_cbsd(session, request_json, cbsd_id)
def _change_cbsd_state(cbsd: DBCbsd, session: Session, new_state: str) -> None: if not cbsd: return state = session.query(DBCbsdState).filter( DBCbsdState.name == new_state, ).scalar() print(f"Changing {cbsd=} {cbsd.state=} to {new_state=}") cbsd.state = state
def _get_or_create_cbsd(self, session: Session, request: CBSDRequest) -> DBCbsd: cbsd = session.query(DBCbsd).filter( DBCbsd.cbsd_serial_number == request.serial_number, ).first() if cbsd: self._update_fields_from_request(cbsd, request) return cbsd unregistered_state = session.query(DBCbsdState). \ filter(DBCbsdState.name == CbsdStates.UNREGISTERED.value).first() cbsd = DBCbsd( cbsd_serial_number=request.serial_number, state=unregistered_state, ) self._update_fields_from_request(cbsd, request) session.add(cbsd) return cbsd
def _list_cbsds(session: Session) -> State: # It might be possible to use join instead of nested queries # however it requires some serious investigation on how to use it # with eager_contains and filter (aliases) db_grant_idle_state_id = session.query(DBGrantState.id).filter( DBGrantState.name == GrantStates.IDLE.value, ).scalar_subquery() db_request_pending_state_id = session.query(DBRequestState.id).filter( DBRequestState.name == RequestStates.PENDING.value, ).scalar_subquery() not_null = [ DBCbsd.fcc_id, DBCbsd.user_id, DBCbsd.number_of_ports, DBCbsd.antenna_gain, DBCbsd.min_power, DBCbsd.max_power, ] query_filter = [field != None for field in not_null] + [DBRequest.id == None] # noqa: E711 # Selectively load sqlalchemy object relations using a single query to avoid commit races. # We want to have CBSD entity "grants" relation only contain grants in a Non-IDLE state. # We want to have CBSD entity "requests" relation only contain PENDING requests. return ( session.query(DBCbsd). join(DBActiveModeConfig). outerjoin( DBGrant, and_( DBGrant.cbsd_id == DBCbsd.id, DBGrant.state_id != db_grant_idle_state_id, ), ). outerjoin( DBRequest, and_( DBRequest.cbsd_id == DBCbsd.id, DBRequest.state_id == db_request_pending_state_id, ), ). options( joinedload(DBCbsd.state), joinedload(DBCbsd.channels), contains_eager(DBCbsd.active_mode_config). joinedload(DBActiveModeConfig.desired_state), contains_eager(DBCbsd.grants). joinedload(DBGrant.state), ). filter(*query_filter). populate_existing() )
def _unregister_cbsd(response: DBResponse, session: Session) -> None: payload = response.request.payload where = _get_cbsd_filter(payload) if not where: return state_id = session.query(DBCbsdState.id). \ filter(DBCbsdState.name == CbsdStates.UNREGISTERED.value) _terminate_all_grants_from_response(response, session) _update_cbsd(session, where, {"state_id": state_id.subquery()})
def _build_state(self, session: Session) -> State: db_configs = session.query(DBActiveModeConfig).join(DBCbsd).options( joinedload(DBActiveModeConfig.cbsd).options( joinedload(DBCbsd.channels), joinedload(DBCbsd.grants).options(joinedload(DBGrant.state)), ), ).filter(*self._get_filter()) configs = [self._build_config(session, x) for x in db_configs] session.commit() return State(active_mode_configs=configs)
def _add_relinquish_requests(self, session: Session, cbsd: DBCbsd) -> None: deregister_request_type = session.query(DBRequestType).filter( DBRequestType.name == RequestTypes.RELINQUISHMENT.value, ).scalar() grants = session.query(DBGrant).join(DBGrantState).filter( DBGrant.cbsd_id == cbsd.id, DBGrantState.name != GrantStates.IDLE.value, ) for grant in grants: request_dict = {"cbsdId": cbsd.cbsd_id, "grantId": grant.grant_id} db_request = DBRequest( type=deregister_request_type, cbsd=cbsd, payload=request_dict, ) session.add(db_request) logger.debug(f"Added {db_request=}.") pass
def _get_channel_related_to_grant(response: DBResponse, session: Session) -> DBChannel: payload = response.request.payload operation_param = payload[OPERATION_PARAM] frequency_range = operation_param["operationFrequencyRange"] channel = session.query(DBChannel).join(DBCbsd).filter( DBCbsd.cbsd_id == payload["cbsdId"], DBChannel.low_frequency == frequency_range["lowFrequency"], DBChannel.high_frequency == frequency_range["highFrequency"], ).scalar() return channel
def _list_cbsds(session: Session) -> State: # It might be possible to use join instead of nested queries # however it requires some serious investigation on how to use it # with eager_contains and filter (aliases) db_grant_idle_state_id = session.query(DBGrantState.id).filter( DBGrantState.name == GrantStates.IDLE.value, ).scalar_subquery() # Selectively load sqlalchemy object relations using a single query to avoid commit races. # We want to have CBSD entity "grants" relation only contain grants in a Non-IDLE state. # We want to have CBSD entity "requests" relation only contain PENDING requests. return (session.query(DBCbsd).outerjoin( DBGrant, and_( DBGrant.cbsd_id == DBCbsd.id, DBGrant.state_id != db_grant_idle_state_id, ), ).outerjoin(DBRequest).options( joinedload(DBCbsd.state), joinedload(DBCbsd.desired_state), joinedload(DBCbsd.channels), contains_eager(DBCbsd.grants).joinedload(DBGrant.state), ).filter(_build_filter()).populate_existing())
def _get_grant_from_response( response: DBResponse, session: Session, ) -> Optional[DBGrant]: cbsd_id = response.cbsd_id grant_id = response.grant_id if not grant_id: return None grant = session.query(DBGrant). \ join(DBCbsd). \ filter(DBCbsd.cbsd_id == cbsd_id, DBGrant.grant_id == grant_id). \ scalar() return grant
def _process_registration_response(cbsd_id: str, response: DBResponse, session: Session): payload = response.request.payload where = _get_cbsd_filter(payload) if not where: return state_id = session.query(DBCbsdState.id). \ filter(DBCbsdState.name == CbsdStates.REGISTERED.value) _update_cbsd(session, where, { "state_id": state_id.subquery(), "cbsd_id": cbsd_id })
def _list_cbsds(session: Session) -> State: # Selectively load sqlalchemy object relations using a single query to avoid commit races. return ( session.query(DBCbsd). outerjoin(DBGrant). outerjoin(DBRequest). options( joinedload(DBCbsd.state), joinedload(DBCbsd.desired_state), joinedload(DBCbsd.channels), contains_eager(DBCbsd.grants). joinedload(DBGrant.state), ). filter(_build_filter()). populate_existing() )
def _create_grant_from_response( response: DBResponse, state: DBGrantState, session: Session, grant_id: str = None, ) -> Optional[DBGrant]: grant_id = grant_id or response.grant_id if not grant_id: return None cbsd_id = session.query( DBCbsd.id).filter(DBCbsd.cbsd_id == response.cbsd_id) grant = DBGrant(cbsd_id=cbsd_id.subquery(), grant_id=grant_id, state=state) _update_grant_from_request(response, grant) session.add(grant) logger.info(f'Created new grant {grant}') return grant
def _create_cbsd(self, session: Session, request_payload: Dict, cbsd_id: Optional[str]): cbsd_state = session.query(DBCbsdState).filter( DBCbsdState.name == CbsdStates.UNREGISTERED.value, ).scalar() user_id = request_payload.get("userId", None) fcc_id = request_payload.get("fccId", None) cbsd_serial_number = request_payload.get("cbsdSerialNumber", None) cbsd = DBCbsd( cbsd_id=cbsd_id, state=cbsd_state, user_id=user_id, fcc_id=fcc_id, cbsd_serial_number=cbsd_serial_number, ) session.add(cbsd) logging.info(f"New CBSD {cbsd=} created.") return cbsd
def process_deregistration_response(obj: ResponseDBProcessor, response: DBResponse, session: Session) -> None: """ Process deregistration response Parameters: obj: Response processor object response: Database response object session: Database session """ _terminate_all_grants_from_response(response, session) cbsd_id = response.payload.get("cbsdId", None) if cbsd_id: cbsd = session.query(DBCbsd).filter(DBCbsd.cbsd_id == cbsd_id).scalar() _change_cbsd_state(cbsd, session, CbsdStates.UNREGISTERED.value)
def _terminate_all_grants_from_response(response: DBResponse, session: Session) -> None: cbsd_id = response.payload.get( CBSD_ID, ) or response.request.payload.get(CBSD_ID) if not cbsd_id: return cbsd = session.query(DBCbsd).filter(DBCbsd.cbsd_id == cbsd_id).scalar() grant_ids = [ grant.id for grant in session.query(DBGrant.id, ).filter( DBGrant.cbsd == cbsd).all() ] if grant_ids: logger.info(f'Disconnecting responses from grants for {cbsd_id=}') session.query(DBResponse).filter(DBResponse.grant_id.in_( grant_ids, ), ).update({DBResponse.grant_id: None}) logger.info(f'Terminating all grants for {cbsd_id=}') session.query(DBGrant).filter(DBGrant.cbsd == cbsd).delete() logger.info(f"Deleting all channels for {cbsd_id=}") session.query(DBChannel).filter(DBChannel.cbsd == cbsd).delete()
def _unsync_conflict_from_response(obj: ResponseDBProcessor, response: DBResponse, session: Session) -> None: state = obj.grant_states_map[GrantStates.UNSYNC.value] conflicts_ids = response.payload.get("response", {}).get("responseData", []) existing_grants = session.query(DBGrant.grant_id).filter( DBGrant.grant_id.in_(conflicts_ids)).all() existing_grants_ids = {g.grant_id for g in existing_grants} for grant_id in conflicts_ids: if grant_id in existing_grants_ids: continue _create_grant_from_response(response, state, session, grant_id=grant_id) return
def _create_channels(response: DBResponse, session: Session): _terminate_all_grants_from_response(response, session) cbsd_id = response.request.payload["cbsdId"] cbsd = session.query(DBCbsd).filter(DBCbsd.cbsd_id == cbsd_id).scalar() available_channels = response.payload.get("availableChannel") if not available_channels: logger.warning( "Could not create channel from spectrumInquiryResponse. Response missing 'availableChannel' object", ) return for ac in available_channels: frequency_range = ac["frequencyRange"] channel = DBChannel( cbsd=cbsd, low_frequency=frequency_range["lowFrequency"], high_frequency=frequency_range["highFrequency"], channel_type=ac["channelType"], rule_applied=ac["ruleApplied"], max_eirp=ac.get("maxEirp"), ) logger.info(f"Creating channel for {cbsd=}") session.add(channel)
def _change_cbsd_state(cbsd: DBCbsd, session: Session, new_state: str) -> None: state = session.query(DBCbsdState).filter( DBCbsdState.name == new_state, ).scalar() cbsd.state = state
def _find_cbsd_from_registration_request(session: Session, payload: Dict) -> DBCbsd: return session.query(DBCbsd).filter( DBCbsd.cbsd_serial_number == payload["cbsdSerialNumber"], ).scalar()
def _set_desired_state_to_registered(self, session: Session, cbsd: DBCbsd) -> None: registered_state = session.query(DBCbsdState). \ filter(DBCbsdState.name == CbsdStates.REGISTERED.value).first() cbsd.desired_state = registered_state