diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea99f16a..464646bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,48 @@
# Changelog
+## v1.6.0
+
+***Summary:***
+
+> - *This version focuses on specific enchancements for both AsSessionWithQoS and Monitoring Event APIs*
+> - *AsSessionWithQoS API*:
+> - *Provision of periodic reports, (NetApp indicates the reporting period in sec)*
+> - *MonitoringEvent API*:
+> - *Addition of LOSS_OF_CONNECTIVITY event, Network detects that the UE is no longer reachable for either signalling or user plane communication. The NetApp may provide a Maximum Detection Time, which indicates the maximum period of time without any communication with the UE (after the UE is considered to be unreachable by the network)*
+> - *Addition of UE_REACHABILITY event, which indicates when the UE becomes reachable (for sending downlink data to the UE)*
+
+
+## UI changes
+
+ - 👉 replace common html blocks with reusable Jinja2 templates (header, sidebar, footer)
+
+
+## Backend
+
+
+ - ➕ Addition of two events on `MonitoringEvent API` ➡`/api/v1/3gpp-monitoring-event/v1/{scsAsId}/subscriptions` 👇
+ - `LOSS_OF_CONNECTIVITY` event
+ - `UE_REACHABILITY` event
+- âž• Addition of periodic reports for `AsSessionWithQoS API` âž¡`/api/v1/3gpp-as-session-with-qos/v1/{scsAsId}/subscriptions`
+
+
+
+## Database
+
+ - Optimization on MongoDB 👇
+ - MongoClient instance from pymongo module is created once in `backend/app/app/db/session.py`
+
+
+
+## Other
+
+ - ✔ `make logs-backend` : display the logs only in the backend service
+ - ✔ `make logs-mongo` : display the logs only for mongo service
+
+
+
+
+
## v1.5.0
***Summary:***
diff --git a/Makefile b/Makefile
index 0c2ba80e..a592fdab 100644
--- a/Makefile
+++ b/Makefile
@@ -38,6 +38,12 @@ build-no-cache:
logs:
docker-compose logs -f
+logs-backend:
+ docker-compose logs -f backend
+
+logs-mongo:
+ docker-compose logs -f mongo
+
ps:
docker ps -a
diff --git a/backend/app/app/api/api_v1/endpoints/monitoringevent.py b/backend/app/app/api/api_v1/endpoints/monitoringevent.py
index d206941d..bd6001c1 100644
--- a/backend/app/app/api/api_v1/endpoints/monitoringevent.py
+++ b/backend/app/app/api/api_v1/endpoints/monitoringevent.py
@@ -8,6 +8,7 @@
from app.crud import crud_mongo, user, ue
from app.api import deps
from app import tools
+from app.db.session import client
from app.api.api_v1.endpoints.utils import add_notifications
from .ue_movement import retrieve_ue_state, retrieve_ue
@@ -18,13 +19,14 @@
def read_active_subscriptions(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that read all the subscriptions", example="myNetapp"),
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Read all active subscriptions
"""
+ db_mongo = client.fastapi
+
retrieved_docs = crud_mongo.read_all(db_mongo, db_collection, current_user.id)
temp_json_subs = retrieved_docs.copy() #Create copy of the list (json_subs) -> you cannot remove items from a list while you iterating the list.
@@ -58,7 +60,6 @@ def create_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
db: Session = Depends(deps.get_db),
- db_mongo: Database = Depends(deps.get_mongo_db),
item_in: schemas.MonitoringEventSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
@@ -66,6 +67,8 @@ def create_subscription(
"""
Create new subscription.
"""
+ db_mongo = client.fastapi
+
UE = ue.get_externalId(db=db, externalId=str(item_in.externalId), owner_id=current_user.id)
if not UE:
raise HTTPException(status_code=409, detail="UE with this external identifier doesn't exist")
@@ -122,7 +125,7 @@ def create_subscription(
add_notifications(http_request, http_response, False)
return http_response
- elif item_in.monitoringType == "LOSS_OF_CONNECTIVITY" and item_in.maximumNumberOfReports == 1:
+ elif (item_in.monitoringType == "LOSS_OF_CONNECTIVITY" or item_in.monitoringType == "UE_REACHABILITY") and item_in.maximumNumberOfReports == 1:
return JSONResponse(content=jsonable_encoder(
{
"title" : "The requested parameters are out of range",
@@ -132,7 +135,7 @@ def create_subscription(
}
}
), status_code=403)
- elif item_in.monitoringType == "LOSS_OF_CONNECTIVITY" and item_in.maximumNumberOfReports > 1:
+ elif (item_in.monitoringType == "LOSS_OF_CONNECTIVITY" or item_in.monitoringType == "UE_REACHABILITY") and item_in.maximumNumberOfReports > 1:
#Check if subscription with externalid && monitoringType exists
if crud_mongo.read_by_multiple_pairs(db_mongo, db_collection, externalId = item_in.externalId, monitoringType = item_in.monitoringType):
raise HTTPException(status_code=409, detail=f"There is already an active subscription for UE with external id {item_in.externalId} - Monitoring Type = {item_in.monitoringType}")
@@ -165,7 +168,6 @@ def update_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
- db_mongo: Database = Depends(deps.get_mongo_db),
item_in: schemas.MonitoringEventSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
@@ -173,6 +175,8 @@ def update_subscription(
"""
Update/Replace an existing subscription resource
"""
+ db_mongo = client.fastapi
+
try:
retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId)
except Exception as ex:
@@ -209,13 +213,14 @@ def read_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Get subscription by id
"""
+ db_mongo = client.fastapi
+
try:
retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId)
except Exception as ex:
@@ -245,13 +250,14 @@ def delete_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Delete a subscription
"""
+ db_mongo = client.fastapi
+
try:
retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId)
except Exception as ex:
diff --git a/backend/app/app/api/api_v1/endpoints/qosInformation.py b/backend/app/app/api/api_v1/endpoints/qosInformation.py
index 3ddfc74c..517b8557 100644
--- a/backend/app/app/api/api_v1/endpoints/qosInformation.py
+++ b/backend/app/app/api/api_v1/endpoints/qosInformation.py
@@ -3,7 +3,7 @@
from fastapi.responses import JSONResponse
from pymongo.database import Database
from sqlalchemy.orm.session import Session
-
+from app.db.session import client
from app import models
from app.api import deps
from app.core.config import qosSettings
@@ -53,12 +53,13 @@ def read_qos_active_profiles(
gNB_id: str = Path(..., title="The ID of the gNB", example="AAAAA1"),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request,
- db_mongo: Database = Depends(deps.get_mongo_db),
db: Session = Depends(deps.get_db)
) -> Any:
"""
Get the available QoS Characteristics
"""
+ db_mongo = client.fastapi
+
gNB = gnb.get_gNB_id(db=db, id=gNB_id)
if not gNB:
raise HTTPException(status_code=404, detail="gNB not found")
diff --git a/backend/app/app/api/api_v1/endpoints/qosMonitoring.py b/backend/app/app/api/api_v1/endpoints/qosMonitoring.py
index a8172320..fdeb3e33 100644
--- a/backend/app/app/api/api_v1/endpoints/qosMonitoring.py
+++ b/backend/app/app/api/api_v1/endpoints/qosMonitoring.py
@@ -8,6 +8,7 @@
from app import models, schemas
from app.api import deps
from app.crud import crud_mongo, user, ue
+from app.db.session import client
from .utils import add_notifications
from .qosInformation import qos_reference_match
@@ -18,13 +19,13 @@
def read_active_subscriptions(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Get subscription by id
"""
+ db_mongo = client.fastapi
retrieved_docs = crud_mongo.read_all(db_mongo, db_collection, current_user.id)
#Check if there are any active subscriptions
@@ -47,22 +48,25 @@ def monitoring_notification(body: schemas.UserPlaneNotificationData):
def create_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
- db_mongo: Database = Depends(deps.get_mongo_db),
db: Session = Depends(deps.get_db),
item_in: schemas.AsSessionWithQoSSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
+ db_mongo = client.fastapi
+
json_request = jsonable_encoder(item_in)
#Currently only EVENT_TRIGGERED is supported
fiveG_qi = qos_reference_match(item_in.qosReference)
if fiveG_qi.get('type') == 'GBR' or fiveG_qi.get('type') == 'DC-GBR':
if (json_request['qosMonInfo'] == None) or (json_request['qosMonInfo']['repFreqs'] == None):
raise HTTPException(status_code=400, detail="Please enter a value in repFreqs field")
- else:
- if 'EVENT_TRIGGERED' not in json_request['qosMonInfo']['repFreqs']:
- raise HTTPException(status_code=400, detail="Only 'EVENT_TRIGGERED' reporting frequency is supported at the current version. Please enter 'EVENT_TRIGGERED' in repFreqs field")
+
+ print(f'------------------------------------Curl from script {item_in.ipv4Addr}')
+ # else:
+ # if 'EVENT_TRIGGERED' not in json_request['qosMonInfo']['repFreqs']:
+ # raise HTTPException(status_code=400, detail="Only 'EVENT_TRIGGERED' reporting frequency is supported at the current version. Please enter 'EVENT_TRIGGERED' in repFreqs field")
#Ensure that the user sends only one of the ipv4, ipv6, macAddr fields
@@ -134,13 +138,13 @@ def read_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Get subscription by id
"""
+ db_mongo = client.fastapi
try:
retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId)
@@ -165,13 +169,13 @@ def update_subscription(
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
item_in: schemas.AsSessionWithQoSSubscriptionCreate,
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Update subscription by id
"""
+ db_mongo = client.fastapi
try:
retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId)
@@ -201,13 +205,14 @@ def delete_subscription(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
- db_mongo: Database = Depends(deps.get_mongo_db),
current_user: models.User = Depends(deps.get_current_active_user),
http_request: Request
) -> Any:
"""
Delete a subscription
"""
+ db_mongo = client.fastapi
+
try:
retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId)
except Exception as ex:
diff --git a/backend/app/app/api/api_v1/endpoints/ue_movement.py b/backend/app/app/api/api_v1/endpoints/ue_movement.py
index af639083..b6d6f525 100644
--- a/backend/app/app/api/api_v1/endpoints/ue_movement.py
+++ b/backend/app/app/api/api_v1/endpoints/ue_movement.py
@@ -7,10 +7,11 @@
from app.crud import crud_mongo
from app.tools.distance import check_distance
from app.tools import qos_callback
-from app.db.session import SessionLocal
+from app.db.session import SessionLocal, client
from app.api import deps
from app.schemas import Msg
from app.tools import monitoring_callbacks, timer
+from sqlalchemy.orm import Session
#Dictionary holding threads that are running per user id.
threads = {}
@@ -25,20 +26,26 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):
self._args = args
self._kwargs = kwargs
self._stop_threads = False
+ self._db = SessionLocal()
return
def run(self):
current_user = self._args[0]
supi = self._args[1]
+
+ active_subscriptions = {
+ "location_reporting" : False,
+ "ue_reachability" : False,
+ "loss_of_connectivity" : False,
+ "as_session_with_qos" : False
+ }
try:
- db = SessionLocal()
- client = MongoClient("mongodb://mongo:27017", username='root', password='pass')
db_mongo = client.fastapi
#Initiate UE - if exists
- UE = crud.ue.get_supi(db=db, supi=supi)
+ UE = crud.ue.get_supi(db=self._db, supi=supi)
if not UE:
logging.warning("UE not found")
threads.pop(f"{supi}")
@@ -63,7 +70,7 @@ def run(self):
#Retrieve paths & points
- path = crud.path.get(db=db, id=UE.path_id)
+ path = crud.path.get(db=self._db, id=UE.path_id)
if not path:
logging.warning("Path not found")
threads.pop(f"{supi}")
@@ -73,16 +80,19 @@ def run(self):
threads.pop(f"{supi}")
return
- points = crud.points.get_points(db=db, path_id=UE.path_id)
+ points = crud.points.get_points(db=self._db, path_id=UE.path_id)
points = jsonable_encoder(points)
#Retrieve all the cells
- Cells = crud.cell.get_multi_by_owner(db=db, owner_id=current_user.id, skip=0, limit=100)
+ Cells = crud.cell.get_multi_by_owner(db=self._db, owner_id=current_user.id, skip=0, limit=100)
json_cells = jsonable_encoder(Cells)
is_superuser = crud.user.is_superuser(current_user)
- t = timer.Timer()
+ t = timer.SequencialTimer(logger=logging.critical)
+
+ # global loss_of_connectivity_ack
+ loss_of_connectivity_ack = "FALSE"
'''
===================================================================
2nd Approach for updating UEs position
@@ -134,79 +144,164 @@ def run(self):
logging.warning("Failed to update coordinates")
logging.warning(ex)
+
+ #MonitoringEvent API - Loss of connectivity
+ if not active_subscriptions.get("loss_of_connectivity"):
+ loss_of_connectivity_sub = crud_mongo.read_by_multiple_pairs(db_mongo, "MonitoringEvent", externalId = UE.external_identifier, monitoringType = "LOSS_OF_CONNECTIVITY")
+ if loss_of_connectivity_sub:
+ active_subscriptions.update({"loss_of_connectivity" : True})
+
+
+ #Validation of subscription
+ if active_subscriptions.get("loss_of_connectivity") and loss_of_connectivity_ack == "FALSE":
+ sub_is_valid = monitoring_event_sub_validation(loss_of_connectivity_sub, is_superuser, current_user.id, loss_of_connectivity_sub.get("owner_id"))
+ if sub_is_valid:
+ try:
+ try:
+ elapsed_time = t.status()
+ if elapsed_time > loss_of_connectivity_sub.get("maximumDetectionTime"):
+ response = monitoring_callbacks.loss_of_connectivity_callback(ues[f"{supi}"], loss_of_connectivity_sub.get("notificationDestination"), loss_of_connectivity_sub.get("link"))
+
+ logging.critical(response.json())
+ #This ack is used to send one time the loss of connectivity callback
+ loss_of_connectivity_ack = response.json().get("ack")
+
+ loss_of_connectivity_sub.update({"maximumNumberOfReports" : loss_of_connectivity_sub.get("maximumNumberOfReports") - 1})
+ crud_mongo.update(db_mongo, "MonitoringEvent", loss_of_connectivity_sub.get("_id"), loss_of_connectivity_sub)
+ except timer.TimerError as ex:
+ # logging.critical(ex)
+ pass
+ except requests.exceptions.ConnectionError as ex:
+ logging.warning("Failed to send the callback request")
+ logging.warning(ex)
+ crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", loss_of_connectivity_sub.get("_id"))
+ active_subscriptions.update({"loss_of_connectivity" : False})
+ continue
+ else:
+ crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", loss_of_connectivity_sub.get("_id"))
+ active_subscriptions.update({"loss_of_connectivity" : False})
+ logging.warning("Subscription has expired")
+ #MonitoringEvent API - Loss of connectivity
+
+ #As Session With QoS API - search for active subscription in db
+ if not active_subscriptions.get("as_session_with_qos"):
+ qos_sub = crud_mongo.read(db_mongo, 'QoSMonitoring', 'ipv4Addr', UE.ip_address_v4)
+ if qos_sub:
+ active_subscriptions.update({"as_session_with_qos" : True})
+ reporting_freq = qos_sub["qosMonInfo"]["repFreqs"]
+ reporting_period = qos_sub["qosMonInfo"]["repPeriod"]
+ if "PERIODIC" in reporting_freq:
+ rt = timer.RepeatedTimer(reporting_period, qos_callback.qos_notification_control, qos_sub, ues[f"{supi}"]["ip_address_v4"], ues.copy(), ues[f"{supi}"])
+ # qos_callback.qos_notification_control(qos_sub, ues[f"{supi}"]["ip_address_v4"], ues.copy(), ues[f"{supi}"])
+
+
+ #If the document exists then validate the owner
+ if not is_superuser and (qos_sub['owner_id'] != current_user.id):
+ logging.warning("Not enough permissions")
+ active_subscriptions.update({"as_session_with_qos" : False})
+ #As Session With QoS API - search for active subscription in db
+
if cell_now != None:
try:
t.stop()
+ loss_of_connectivity_ack = "FALSE"
+ rt.start()
except timer.TimerError as ex:
# logging.critical(ex)
pass
# if UE.Cell_id != cell_now.get('id'): #Cell has changed in the db "handover"
if ues[f"{supi}"]["Cell_id"] != cell_now.get('id'): #Cell has changed in the db "handover"
+
+ #Monitoring Event API - UE reachability
+ #check if the ue was disconnected before
+ if ues[f"{supi}"]["Cell_id"] == None:
+
+ if not active_subscriptions.get("ue_reachability"):
+ ue_reachability_sub = crud_mongo.read_by_multiple_pairs(db_mongo, "MonitoringEvent", externalId = UE.external_identifier, monitoringType = "UE_REACHABILITY")
+ if ue_reachability_sub:
+ active_subscriptions.update({"ue_reachability" : True})
+
+ #Validation of subscription
+
+ if active_subscriptions.get("ue_reachability"):
+ sub_is_valid = monitoring_event_sub_validation(ue_reachability_sub, is_superuser, current_user.id, ue_reachability_sub.get("owner_id"))
+ if sub_is_valid:
+ try:
+ try:
+ monitoring_callbacks.ue_reachability_callback(ues[f"{supi}"], ue_reachability_sub.get("notificationDestination"), ue_reachability_sub.get("link"), ue_reachability_sub.get("reachabilityType"))
+ ue_reachability_sub.update({"maximumNumberOfReports" : ue_reachability_sub.get("maximumNumberOfReports") - 1})
+ crud_mongo.update(db_mongo, "MonitoringEvent", ue_reachability_sub.get("_id"), ue_reachability_sub)
+ except timer.TimerError as ex:
+ # logging.critical(ex)
+ pass
+ except requests.exceptions.ConnectionError as ex:
+ logging.warning("Failed to send the callback request")
+ logging.warning(ex)
+ crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", ue_reachability_sub.get("_id"))
+ active_subscriptions.update({"ue_reachability" : False})
+ continue
+ else:
+ crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", ue_reachability_sub.get("_id"))
+ active_subscriptions.update({"ue_reachability" : False})
+ logging.warning("Subscription has expired")
+ #Monitoring Event API - UE reachability
+
+
# logging.warning(f"UE({UE.supi}) with ipv4 {UE.ip_address_v4} handovers to Cell {cell_now.get('id')}, {cell_now.get('description')}")
- # crud.ue.update(db=db, db_obj=UE, obj_in={"Cell_id" : cell_now.get('id')})
ues[f"{supi}"]["Cell_id"] = cell_now.get('id')
ues[f"{supi}"]["cell_id_hex"] = cell_now.get('cell_id')
- gnb = crud.gnb.get(db=db, id=cell_now.get("gNB_id"))
+ gnb = crud.gnb.get(db=self._db, id=cell_now.get("gNB_id"))
ues[f"{supi}"]["gnb_id_hex"] = gnb.gNB_id
- #Retrieve the subscription of the UE by external Id | This could be outside while true but then the user cannot subscribe when the loop runs
- # sub = crud.monitoring.get_sub_externalId(db=db, externalId=UE.external_identifier, owner_id=current_user.id)
- sub = crud_mongo.read(db_mongo, "MonitoringEvent", "externalId", UE.external_identifier)
- #Validation of subscription
- if not sub:
- # logging.warning("Monitoring Event subscription not found")
- pass
- elif not is_superuser and (sub.get("owner_id") != current_user.id):
- # logging.warning("Not enough permissions")
- pass
- else:
- sub_validate_time = tools.check_expiration_time(expire_time=sub.get("monitorExpireTime"))
- if sub_validate_time:
- sub = tools.check_numberOfReports(db_mongo, sub)
- if sub: #return the callback request only if subscription is valid
+ #Monitoring Event API - Location Reporting
+ #Retrieve the subscription of the UE by external Id | This could be outside while true but then the user cannot subscribe when the loop runs
+ if not active_subscriptions.get("location_reporting"):
+ location_reporting_sub = crud_mongo.read_by_multiple_pairs(db_mongo, "MonitoringEvent", externalId = UE.external_identifier, monitoringType = "LOCATION_REPORTING")
+ if location_reporting_sub:
+ active_subscriptions.update({"location_reporting" : True})
+
+ #Validation of subscription
+ if active_subscriptions.get("location_reporting"):
+ sub_is_valid = monitoring_event_sub_validation(location_reporting_sub, is_superuser, current_user.id, location_reporting_sub.get("owner_id"))
+ if sub_is_valid:
+ try:
try:
- response = monitoring_callbacks.location_callback(ues[f"{supi}"], sub.get("notificationDestination"), sub.get("link"))
- # logging.info(response.json())
- except requests.exceptions.ConnectionError as ex:
- logging.warning("Failed to send the callback request")
- logging.warning(ex)
- crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", sub.get("_id"))
- continue
+ monitoring_callbacks.location_callback(ues[f"{supi}"], location_reporting_sub.get("notificationDestination"), location_reporting_sub.get("link"))
+ location_reporting_sub.update({"maximumNumberOfReports" : location_reporting_sub.get("maximumNumberOfReports") - 1})
+ crud_mongo.update(db_mongo, "MonitoringEvent", location_reporting_sub.get("_id"), location_reporting_sub)
+ except timer.TimerError as ex:
+ # logging.critical(ex)
+ pass
+ except requests.exceptions.ConnectionError as ex:
+ logging.warning("Failed to send the callback request")
+ logging.warning(ex)
+ crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", location_reporting_sub.get("_id"))
+ active_subscriptions.update({"location_reporting" : False})
+ continue
else:
- crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", sub.get("_id"))
- logging.warning("Subscription has expired (expiration date)")
-
- #QoS Monitoring Event (handover)
- # ues_connected = crud.ue.get_by_Cell(db=db, cell_id=UE.Cell_id)
- ues_connected = 0
- # temp_ues = ues.copy()
- # for ue in temp_ues:
- # # print(ue)
- # if ues[ue]["Cell_id"] == ues[f"{supi}"]["Cell_id"]:
- # ues_connected += 1
-
- #subtract 1 for the UE that is currently running. We are looking for other ues that are currently connected in the same cell
- ues_connected -= 1
-
- if ues_connected > 1:
- gbr = 'QOS_NOT_GUARANTEED'
- else:
- gbr = 'QOS_GUARANTEED'
-
- # logging.warning(gbr)
- # qos_notification_control(gbr ,current_user, UE.ip_address_v4)
- qos_callback.qos_notification_control(current_user, ues[f"{supi}"]["ip_address_v4"], ues.copy(), ues[f"{supi}"])
-
+ crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", location_reporting_sub.get("_id"))
+ active_subscriptions.update({"location_reporting" : False})
+ logging.warning("Subscription has expired")
+ #Monitoring Event API - Location Reporting
+
+ #As Session With QoS API - if EVENT_TRIGGER then send callback on handover
+ if active_subscriptions.get("as_session_with_qos"):
+ reporting_freq = qos_sub["qosMonInfo"]["repFreqs"]
+ if "EVENT_TRIGGERED" in reporting_freq:
+ qos_callback.qos_notification_control(qos_sub, ues[f"{supi}"]["ip_address_v4"], ues.copy(), ues[f"{supi}"])
+ #As Session With QoS API - if EVENT_TRIGGER then send callback on handover
+
else:
# crud.ue.update(db=db, db_obj=UE, obj_in={"Cell_id" : None})
try:
t.start()
+ rt.stop()
except timer.TimerError as ex:
# logging.critical(ex)
pass
-
+
ues[f"{supi}"]["Cell_id"] = None
ues[f"{supi}"]["cell_id_hex"] = None
ues[f"{supi}"]["gnb_id_hex"] = None
@@ -227,9 +322,11 @@ def run(self):
if self._stop_threads:
logging.critical("Terminating thread...")
- crud.ue.update_coordinates(db=db, lat=ues[f"{supi}"]["latitude"], long=ues[f"{supi}"]["longitude"], db_obj=UE)
- crud.ue.update(db=db, db_obj=UE, obj_in={"Cell_id" : ues[f"{supi}"]["Cell_id"]})
+ crud.ue.update_coordinates(db=self._db, lat=ues[f"{supi}"]["latitude"], long=ues[f"{supi}"]["longitude"], db_obj=UE)
+ crud.ue.update(db=self._db, db_obj=UE, obj_in={"Cell_id" : ues[f"{supi}"]["Cell_id"]})
ues.pop(f"{supi}")
+ self._db.close()
+ rt.stop()
break
# End of 2nd Approach for updating UEs position
@@ -275,58 +372,7 @@ def run(self):
# continue
- # try:
- # UE = crud.ue.update_coordinates(db=db, lat=point["latitude"], long=point["longitude"], db_obj=UE)
- # cell_now = check_distance(UE.latitude, UE.longitude, json_cells) #calculate the distance from all the cells
- # except Exception as ex:
- # logging.warning("Failed to update coordinates")
- # logging.warning(ex)
-
- # if cell_now != None:
- # if UE.Cell_id != cell_now.get('id'): #Cell has changed in the db "handover"
- # logging.warning(f"UE({UE.supi}) with ipv4 {UE.ip_address_v4} handovers to Cell {cell_now.get('id')}, {cell_now.get('description')}")
- # crud.ue.update(db=db, db_obj=UE, obj_in={"Cell_id" : cell_now.get('id')})
-
- # #Retrieve the subscription of the UE by external Id | This could be outside while true but then the user cannot subscribe when the loop runs
- # # sub = crud.monitoring.get_sub_externalId(db=db, externalId=UE.external_identifier, owner_id=current_user.id)
- # sub = crud_mongo.read(db_mongo, "MonitoringEvent", "externalId", UE.external_identifier)
-
- # #Validation of subscription
- # if not sub:
- # logging.warning("Monitoring Event subscription not found")
- # elif not crud.user.is_superuser(current_user) and (sub.get("owner_id") != current_user.id):
- # logging.warning("Not enough permissions")
- # else:
- # sub_validate_time = tools.check_expiration_time(expire_time=sub.get("monitorExpireTime"))
- # if sub_validate_time:
- # sub = tools.check_numberOfReports(db_mongo, sub)
- # if sub: #return the callback request only if subscription is valid
- # try:
- # response = location_callback(UE, sub.get("notificationDestination"), sub.get("link"))
- # logging.info(response.json())
- # except requests.exceptions.ConnectionError as ex:
- # logging.warning("Failed to send the callback request")
- # logging.warning(ex)
- # crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", sub.get("_id"))
- # continue
- # else:
- # crud_mongo.delete_by_uuid(db_mongo, "MonitoringEvent", sub.get("_id"))
- # logging.warning("Subscription has expired (expiration date)")
-
- # #QoS Monitoring Event (handover)
- # ues_connected = crud.ue.get_by_Cell(db=db, cell_id=UE.Cell_id)
- # if len(ues_connected) > 1:
- # gbr = 'QOS_NOT_GUARANTEED'
- # else:
- # gbr = 'QOS_GUARANTEED'
-
- # logging.warning(gbr)
- # qos_notification_control(gbr ,current_user, UE.ip_address_v4)
- # logging.critical("Bypassed qos notification control")
- # else:
- # crud.ue.update(db=db, db_obj=UE, obj_in={"Cell_id" : None})
-
- # # logging.info(f'User: {current_user.id} | UE: {supi} | Current location: latitude ={UE.latitude} | longitude = {UE.longitude} | Speed: {UE.speed}' )
+ # #-----------------------Code goes here-------------------------#
# if UE.speed == 'LOW':
# time.sleep(1)
@@ -340,9 +386,9 @@ def run(self):
# if self._stop_threads:
# print("Terminating thread...")
# break
- finally:
- db.close()
- return
+
+ except Exception as ex:
+ logging.critical(ex)
def stop(self):
self._stop_threads = True
@@ -421,3 +467,15 @@ def retrieve_ue(supi: str) -> dict:
return ues.get(supi)
+def monitoring_event_sub_validation(sub: dict, is_superuser: bool, current_user_id: int, owner_id) -> bool:
+
+ if not is_superuser and (owner_id != current_user_id):
+ # logging.warning("Not enough permissions")
+ return False
+ else:
+ sub_validate_time = tools.check_expiration_time(expire_time=sub.get("monitorExpireTime"))
+ sub_validate_number_of_reports = tools.check_numberOfReports(sub.get("maximumNumberOfReports"))
+ if sub_validate_time and sub_validate_number_of_reports:
+ return True
+ else:
+ return False
\ No newline at end of file
diff --git a/backend/app/app/api/deps.py b/backend/app/app/api/deps.py
index 75fd4933..6247ae0f 100644
--- a/backend/app/app/api/deps.py
+++ b/backend/app/app/api/deps.py
@@ -1,5 +1,4 @@
from typing import Generator
-from pymongo import MongoClient
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
@@ -23,14 +22,6 @@ def get_db() -> Generator:
finally:
db.close() #The code following the yield statement is executed after the response has been delivered
-#Dependency for mongoDB
-
-def get_mongo_db():
- client = MongoClient("mongodb://mongo:27017", username='root', password='pass')
- db = client.fastapi
- return db
-
-
def get_current_user(
db: Session = Depends(get_db), token: str = Depends(reusable_oauth2)
) -> models.User:
diff --git a/backend/app/app/db/session.py b/backend/app/app/db/session.py
index 1e6b7f18..7b5b00be 100644
--- a/backend/app/app/db/session.py
+++ b/backend/app/app/db/session.py
@@ -1,7 +1,10 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
-
+from pymongo import MongoClient
from app.core.config import settings
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True, pool_size=150, max_overflow=20) #Create a db URL for SQLAlchemy in core/config.py/ Settings class
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) #Each instance is a db session
+
+
+client = MongoClient("mongodb://mongo:27017", username='root', password='pass')
diff --git a/backend/app/app/schemas/monitoringevent.py b/backend/app/app/schemas/monitoringevent.py
index 1f844862..272ad84a 100644
--- a/backend/app/app/schemas/monitoringevent.py
+++ b/backend/app/app/schemas/monitoringevent.py
@@ -21,7 +21,11 @@ class LocationInfo(BaseModel):
class MonitoringType(str, Enum):
locationReporting = "LOCATION_REPORTING"
lossOfConnectivity = "LOSS_OF_CONNECTIVITY"
+ ueReachability = "UE_REACHABILITY"
+class ReachabilityType(str, Enum):
+ sms = "SMS"
+ data = "DATA"
class MonitoringEventReport(BaseModel):
# msisdn: Optional[str] = None
@@ -44,8 +48,8 @@ class MonitoringEventSubscriptionCreate(BaseModel):
monitoringType: MonitoringType
maximumNumberOfReports: Optional[int] = Field(None, description="Identifies the maximum number of event reports to be generated. Value 1 makes the Monitoring Request a One-time Request", ge=1)
monitorExpireTime: Optional[datetime] = Field(None, description="Identifies the absolute time at which the related monitoring event request is considered to expire")
- maximumDetectionTime: Optional[int] = Field(None, description="If monitoringType is \"LOSS_OF_CONNECTIVITY\", this parameter may be included to identify the maximum period of time after which the UE is considered to be unreachable.", gt=0)
- # monitoringEventReport: Optional[MonitoringEventReport] = None
+ maximumDetectionTime: Optional[int] = Field(1, description="If monitoringType is \"LOSS_OF_CONNECTIVITY\", this parameter may be included to identify the maximum period of time after which the UE is considered to be unreachable.", gt=0)
+ reachabilityType: Optional[ReachabilityType] = Field("DATA", description="If monitoringType is \"UE_REACHABILITY\", this parameter shall be included to identify whether the request is for \"Reachability for SMS\" or \"Reachability for Data\"")
class MonitoringEventSubscription(MonitoringEventSubscriptionCreate):
link: Optional[AnyHttpUrl] = Field("https://myresource.com", description="String identifying a referenced resource. This is also returned as a location header in 201 Created Response")
@@ -56,6 +60,7 @@ class Config:
class MonitoringNotification(MonitoringEventReport):
subscription: AnyHttpUrl
lossOfConnectReason: Optional[int] = Field(None, description= "According to 3GPP TS 29.522 the lossOfConnectReason attribute shall be set to 6 if the UE is deregistered, 7 if the maximum detection timer expires or 8 if the UE is purged")
+ reachabilityType: Optional[ReachabilityType] = Field("DATA", description="If monitoringType is \"UE_REACHABILITY\", this parameter shall be included to identify whether the request is for \"Reachability for SMS\" or \"Reachability for Data\"")
class MonitoringEventReportReceived(BaseModel):
ok: bool
\ No newline at end of file
diff --git a/backend/app/app/schemas/qosMonitoring.py b/backend/app/app/schemas/qosMonitoring.py
index cb991896..edfc7abe 100644
--- a/backend/app/app/schemas/qosMonitoring.py
+++ b/backend/app/app/schemas/qosMonitoring.py
@@ -40,7 +40,7 @@ class AsSessionWithQoSSubscriptionCreate(BaseModel):
ipv4Addr: Optional[IPvAnyAddress] = Field(default='10.0.0.0', description="String identifying an Ipv4 address")
ipv6Addr: Optional[IPvAnyAddress] = Field(default="0:0:0:0:0:0:0:0", description="String identifying an Ipv6 address. Default value ::1/128 (loopback)")
macAddr: Optional[constr(regex=r'^([0-9a-fA-F]{2})((-[0-9a-fA-F]{2}){5})$')] = '22-00-00-00-00-00'
- notificationDestination: AnyHttpUrl = Field(..., description="Reference resource (URL) identifying service consumer's endpoint, in order to receive the asynchronous notification. For testing use 'http://localhost:80/api/v1/utils/session-with-qos/callback'") #Default value for development testing
+ notificationDestination: AnyHttpUrl = Field("http://localhost:80/api/v1/utils/session-with-qos/callback", description="Reference resource (URL) identifying service consumer's endpoint, in order to receive the asynchronous notification. For testing use 'http://localhost:80/api/v1/utils/session-with-qos/callback'") #Default value for development testing
snssai: Optional[Snssai] = None
dnn: Optional[str] = Field("province1.mnc01.mcc202.gprs", description="String identifying the Data Network Name (i.e., Access Point Name in 4G). For more information check clause 9A of 3GPP TS 23.003")
qosReference: int = Field(default=9, description="Identifies a pre-defined QoS Information", ge=1, le=90)
diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js
index dabc71a4..04547180 100644
--- a/backend/app/app/static/js/map.js
+++ b/backend/app/app/static/js/map.js
@@ -474,7 +474,7 @@ function ui_map_paint_moving_UEs() {
function api_get_Cells() {
- var url = app.api_url + '/Cells/?skip=0&limit=100';
+ var url = app.api_url + '/Cells?skip=0&limit=1000';
$.ajax({
type: 'GET',
diff --git a/backend/app/app/tools/check_subscription.py b/backend/app/app/tools/check_subscription.py
index 99e74343..974f45cf 100644
--- a/backend/app/app/tools/check_subscription.py
+++ b/backend/app/app/tools/check_subscription.py
@@ -52,17 +52,9 @@ def check_expiration_time(expire_time):
else:
return False
-def check_numberOfReports(db, sub):
- if sub.get("maximumNumberOfReports")>1:
- newNumberOfReports = sub.get("maximumNumberOfReports") - 1
- sub.update({"maximumNumberOfReports" : newNumberOfReports})
- crud_mongo.update(db, "MonitoringEvent", sub.get("_id"), sub)
- return sub
- elif sub.get("maximumNumberOfReports") == 1:
- newNumberOfReports = sub.get("maximumNumberOfReports") - 1
- sub.update({"maximumNumberOfReports" : newNumberOfReports})
- # monitoring.remove(db=db, id=item_in.id)
- crud_mongo.delete_by_uuid(db, "MonitoringEvent", sub.get("_id"))
- return sub
+def check_numberOfReports(maximum_number_of_reports: int) -> bool:
+ if maximum_number_of_reports >= 1:
+ return True
else:
- logging.warning("Subscription has expired (maximum number of reports")
\ No newline at end of file
+ logging.warning("Subscription has expired (maximum number of reports")
+ return False
\ No newline at end of file
diff --git a/backend/app/app/tools/monitoring_callbacks.py b/backend/app/app/tools/monitoring_callbacks.py
index 57991a42..f129cf33 100644
--- a/backend/app/app/tools/monitoring_callbacks.py
+++ b/backend/app/app/tools/monitoring_callbacks.py
@@ -26,7 +26,7 @@ def location_callback(ue, callbackurl, subscription):
return response
-def loss_of_connectivity_callaback(ue, callbackurl, subscription):
+def loss_of_connectivity_callback(ue, callbackurl, subscription):
url = callbackurl
payload = json.dumps({
@@ -34,7 +34,30 @@ def loss_of_connectivity_callaback(ue, callbackurl, subscription):
"ipv4Addr" : ue.get("ip_address_v4"),
"subscription" : subscription,
"monitoringType": "LOSS_OF_CONNECTIVITY",
- "lossOfConnectReason": 8
+ "lossOfConnectReason": 7
+ })
+ headers = {
+ 'accept': 'application/json',
+ 'Content-Type': 'application/json'
+ }
+
+ #Timeout values according to https://docs.python-requests.org/en/master/user/advanced/#timeouts
+ #First value of the tuple "3.05" corresponds to connect and second "27" to read timeouts
+ #(i.e., connect timeout means that the server is unreachable and read that the server is reachable but the client does not receive a response within 27 seconds)
+
+ response = requests.request("POST", url, headers=headers, data=payload, timeout=(3.05, 27))
+
+ return response
+
+def ue_reachability_callback(ue, callbackurl, subscription, reachabilityType):
+ url = callbackurl
+
+ payload = json.dumps({
+ "externalId" : ue.get("external_identifier"),
+ "ipv4Addr" : ue.get("ip_address_v4"),
+ "subscription" : subscription,
+ "monitoringType": "UE_REACHABILITY",
+ "reachabilityType": reachabilityType
})
headers = {
'accept': 'application/json',
diff --git a/backend/app/app/tools/qos_callback.py b/backend/app/app/tools/qos_callback.py
index 564ba18d..2e2c1a92 100644
--- a/backend/app/app/tools/qos_callback.py
+++ b/backend/app/app/tools/qos_callback.py
@@ -1,6 +1,5 @@
import requests, json, logging
-from pymongo import MongoClient
-from app.crud import crud_mongo, user, ue
+from app.crud import ue
from app.api.api_v1.endpoints.qosInformation import qos_reference_match
from app.db.session import SessionLocal
from fastapi.encoders import jsonable_encoder
@@ -52,20 +51,7 @@ def qos_callback(callbackurl, resource, qos_status, ipv4):
return response
-def qos_notification_control(current_user, ipv4, ues: dict, current_ue: dict):
- client = MongoClient("mongodb://mongo:27017", username='root', password='pass')
- db = client.fastapi
-
- doc = crud_mongo.read(db, 'QoSMonitoring', 'ipv4Addr', ipv4)
-
- #Check if the document exists
- if not doc:
- # logging.warning("AsSessionWithQoS subscription not found")
- return
- #If the document exists then validate the owner
- if not user.is_superuser(current_user) and (doc['owner_id'] != current_user.id):
- logging.info("Not enough permissions")
- return
+def qos_notification_control(doc, ipv4, ues: dict, current_ue: dict):
number_of_ues_in_cell = ues_in_cell(ues, current_ue)
diff --git a/backend/app/app/tools/timer.py b/backend/app/app/tools/timer.py
index 58705649..b1bdf0e3 100644
--- a/backend/app/app/tools/timer.py
+++ b/backend/app/app/tools/timer.py
@@ -1,11 +1,17 @@
-import time, logging
+import time
+from threading import Timer
class TimerError(Exception):
"""A custom exception used to report errors in use of Timer class"""
-class Timer:
- def __init__(self):
+class SequencialTimer:
+ def __init__(self,
+ text="Elapsed time: {:0.4f} seconds",
+ logger=print
+ ):
self._start_time = None
+ self.text = text
+ self.logger = logger
def start(self):
"""Start a new timer"""
@@ -20,5 +26,48 @@ def stop(self):
raise TimerError(f"Timer is not running. Use .start() to start it")
elapsed_time = time.perf_counter() - self._start_time
- self._start_time = None
- logging.critical(f"Elapsed time: {elapsed_time:0.4f} seconds")
\ No newline at end of file
+ # Reset the timer
+ self._start_time = None
+
+ if self.logger:
+ self.logger(self.text.format(elapsed_time))
+
+ return elapsed_time
+
+ def status(self):
+ """Report the elapsed time by the time timer has started"""
+ if self._start_time is None:
+ raise TimerError(f"Timer is not running. Use .start() to start it")
+
+ elapsed_time = time.perf_counter() - self._start_time
+
+ if self.logger:
+ self.logger(self.text.format(elapsed_time))
+
+ return elapsed_time
+
+
+class RepeatedTimer(object):
+ def __init__(self, interval, function, *args, **kwargs):
+ self._timer = None
+ self.interval = interval
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+ self.is_running = False
+ self.start()
+
+ def _run(self):
+ self.is_running = False
+ self.start()
+ self.function(*self.args, **self.kwargs)
+
+ def start(self):
+ if not self.is_running:
+ self._timer = Timer(self.interval, self._run)
+ self._timer.start()
+ self.is_running = True
+
+ def stop(self):
+ self._timer.cancel()
+ self.is_running = False
\ No newline at end of file
diff --git a/backend/app/app/ui/dashboard.html b/backend/app/app/ui/dashboard.html
index b106cb12..d4dc0507 100644
--- a/backend/app/app/ui/dashboard.html
+++ b/backend/app/app/ui/dashboard.html
@@ -41,111 +41,13 @@