diff --git a/.gitignore b/.gitignore index 431735d3..8a73fa14 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ *.csr *.pem backend/app/app/core/certificates/capif_provider_details.json +backend/app/app/core/certificates/CAPIF_service_as_session_with_qos.json +backend/app/app/core/certificates/CAPIF_service_monitoring_event.json venv/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 10512512..43d5455b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ # Changelog +## v2.1.0 + +***Summary:*** +> - *Add functionality to support CAPIF Core Function's logging service* +> - *Avoid db connections within the threads (optimisation)* +> - *Fix token causing 403 error in mapbox front-end* +

+ ## v2.0.0 ***Summary:*** diff --git a/Makefile b/Makefile index 60a25378..3a21037a 100644 --- a/Makefile +++ b/Makefile @@ -35,8 +35,11 @@ build: build-no-cache: docker compose --profile debug build --no-cache --pull -logs: - docker compose logs -f +logs-dev: + docker compose --profile dev logs -f + +logs-debug: + docker compose --profile debug logs -f logs-backend: docker compose logs -f backend diff --git a/backend/app/app/api/api_v1/endpoints/monitoringevent.py b/backend/app/app/api/api_v1/endpoints/monitoringevent.py index 34fe296c..f93f7420 100644 --- a/backend/app/app/api/api_v1/endpoints/monitoringevent.py +++ b/backend/app/app/api/api_v1/endpoints/monitoringevent.py @@ -8,8 +8,9 @@ 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 app.api.api_v1.endpoints.utils import add_notifications, ccf_logs from .ue_movement import retrieve_ue_state, retrieve_ue +import logging router = APIRouter() db_collection= 'MonitoringEvent' @@ -41,6 +42,16 @@ def read_active_subscriptions( if retrieved_docs: http_response = JSONResponse(content=retrieved_docs, status_code=200) add_notifications(http_request, http_response, False) + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") + return http_response else: return Response(status_code=204) @@ -72,6 +83,14 @@ def create_subscription( UE = ue.get_externalId(db=db, externalId=str(item_in.externalId), owner_id=current_user.id) if not UE: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "UE with this external identifier doesn't exist"}) + json_response.update({"status_code" : "409"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=409, detail="UE with this external identifier doesn't exist") @@ -98,12 +117,30 @@ def create_subscription( http_response = JSONResponse(content=json_compatible_item_data, status_code=200) add_notifications(http_request, http_response, False) + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response #Subscription elif item_in.monitoringType == "LOCATION_REPORTING" and item_in.maximumNumberOfReports>1: #Check if subscription with externalid exists if crud_mongo.read_by_multiple_pairs(db_mongo, db_collection, externalId = item_in.externalId, monitoringType = item_in.monitoringType): + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : f"There is already an active subscription for UE with external id {item_in.externalId} - Monitoring Type = {item_in.monitoringType}"}) + json_response.update({"status_code" : "409"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") 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}") json_data = jsonable_encoder(item_in.dict(exclude_unset=True)) @@ -126,8 +163,27 @@ def create_subscription( http_response = JSONResponse(content=updated_doc, status_code=201, headers=response_header) add_notifications(http_request, http_response, False) + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response elif (item_in.monitoringType == "LOSS_OF_CONNECTIVITY" or item_in.monitoringType == "UE_REACHABILITY") and item_in.maximumNumberOfReports == 1: + + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "\"maximumNumberOfReports\" should be greater than 1 in case of LOSS_OF_CONNECTIVITY event"}) + json_response.update({"status_code" : "403"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") return JSONResponse(content=jsonable_encoder( { "title" : "The requested parameters are out of range", @@ -140,6 +196,14 @@ def create_subscription( 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): + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : f"There is already an active subscription for UE with external id {item_in.externalId} - Monitoring Type = {item_in.monitoringType}"}) + json_response.update({"status_code" : "409"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") 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}") json_data = jsonable_encoder(item_in.dict(exclude_unset=True)) @@ -161,6 +225,16 @@ def create_subscription( http_response = JSONResponse(content=updated_doc, status_code=201, headers=response_header) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") return http_response @@ -183,10 +257,26 @@ def update_subscription( try: retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId) except Exception as ex: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Please enter a valid uuid (24-character hex string)"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=400, detail='Please enter a valid uuid (24-character hex string)') #Check if the document exists if not retrieved_doc: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Subscription not found"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=404, detail="Subscription not found") #If the document exists then validate the owner if not user.is_superuser(current_user) and (retrieved_doc['owner_id'] != current_user.id): @@ -205,6 +295,17 @@ def update_subscription( http_response = JSONResponse(content=updated_doc, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response else: crud_mongo.delete_by_uuid(db_mongo, db_collection, subscriptionId) @@ -228,10 +329,25 @@ def read_subscription( try: retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId) except Exception as ex: + try: + json_response = {} + json_response.update({"response" : "Please enter a valid uuid (24-character hex string)"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=400, detail='Please enter a valid uuid (24-character hex string)') #Check if the document exists if not retrieved_doc: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Subscription not found"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=404, detail="Subscription not found") #If the document exists then validate the owner if not user.is_superuser(current_user) and (retrieved_doc['owner_id'] != current_user.id): @@ -244,6 +360,17 @@ def read_subscription( http_response = JSONResponse(content=retrieved_doc, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response else: crud_mongo.delete_by_uuid(db_mongo, db_collection, subscriptionId) @@ -266,10 +393,25 @@ def delete_subscription( try: retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId) except Exception as ex: + try: + json_response = {} + json_response.update({"response" : "Please enter a valid uuid (24-character hex string)"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=400, detail='Please enter a valid uuid (24-character hex string)') #Check if the document exists if not retrieved_doc: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Subscription not found"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.critical(f"Error: {error}") raise HTTPException(status_code=404, detail="Subscription not found") #If the document exists then validate the owner if not user.is_superuser(current_user) and (retrieved_doc['owner_id'] != current_user.id): @@ -280,6 +422,17 @@ def delete_subscription( http_response = JSONResponse(content=retrieved_doc, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_monitoring_event.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response diff --git a/backend/app/app/api/api_v1/endpoints/qosMonitoring.py b/backend/app/app/api/api_v1/endpoints/qosMonitoring.py index 206a6f5a..9b428fd0 100644 --- a/backend/app/app/api/api_v1/endpoints/qosMonitoring.py +++ b/backend/app/app/api/api_v1/endpoints/qosMonitoring.py @@ -9,7 +9,7 @@ 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 .utils import add_notifications, ccf_logs from .qosInformation import qos_reference_match router = APIRouter() @@ -31,10 +31,29 @@ def read_active_subscriptions( #Check if there are any active subscriptions if not retrieved_docs: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "There are no active subscriptions"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=404, detail="There are no active subscriptions") http_response = JSONResponse(content=retrieved_docs, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response #Callback @@ -63,13 +82,15 @@ def create_subscription( 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): + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Please enter a value in repFreqs field"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=400, detail="Please enter a value 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 validate_ids(item_in.dict(exclude_unset=True)) @@ -96,6 +117,14 @@ def create_subscription( raise HTTPException(status_code=409, detail="UE not found") if doc and (doc.get("owner_id") == current_user.id): + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : f"Subscription for UE with {selected_id} ({error_var}) already exists"}) + json_response.update({"status_code" : "409"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=409, detail=f"Subscription for UE with {selected_id} ({error_var}) already exists") #Create the document in mongodb @@ -131,7 +160,16 @@ def create_subscription( http_response = JSONResponse(content=updated_doc, status_code=201, headers=response_header) add_notifications(http_request, http_response, False) - + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") return http_response @@ -152,10 +190,26 @@ def read_subscription( try: retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId) except Exception as ex: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Please enter a vvalid uuid (24-character hex string)"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=400, detail='Please enter a valid uuid (24-character hex string)') #Check if the document exists if not retrieved_doc: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Subscription not found"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=404, detail="Subscription not found") #If the document exists then validate the owner if not user.is_superuser(current_user) and (retrieved_doc['owner_id'] != current_user.id): @@ -164,6 +218,17 @@ def read_subscription( retrieved_doc.pop("owner_id") http_response = JSONResponse(content=retrieved_doc, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response @router.put("/{scsAsId}/subscriptions/{subscriptionId}", response_model=schemas.AsSessionWithQoSSubscription) @@ -184,10 +249,26 @@ def update_subscription( try: retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId) except Exception as ex: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Please enter a vvalid uuid (24-character hex string)"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=400, detail='Please enter a valid uuid (24-character hex string)') #Check if the document exists if not retrieved_doc: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Subscription not found"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=404, detail="Subscription not found") #If the document exists then validate the owner if not user.is_superuser(current_user) and (retrieved_doc['owner_id'] != current_user.id): @@ -202,6 +283,17 @@ def update_subscription( updated_doc.pop("owner_id") http_response = JSONResponse(content=updated_doc, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response @router.delete("/{scsAsId}/subscriptions/{subscriptionId}", response_model=schemas.AsSessionWithQoSSubscription) @@ -221,11 +313,27 @@ def delete_subscription( try: retrieved_doc = crud_mongo.read_uuid(db_mongo, db_collection, subscriptionId) except Exception as ex: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Please enter a vvalid uuid (24-character hex string)"}) + json_response.update({"status_code" : "400"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=400, detail='Please enter a valid uuid (24-character hex string)') #Check if the document exists if not retrieved_doc: + #CAPIF Core Function Logging Service + try: + json_response = {} + json_response.update({"response" : "Subscription not found"}) + json_response.update({"status_code" : "404"}) + ccf_logs(http_request, json_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") raise HTTPException(status_code=404, detail="Subscription not found") #If the document exists then validate the owner if not user.is_superuser(current_user) and (retrieved_doc['owner_id'] != current_user.id): @@ -234,6 +342,17 @@ def delete_subscription( crud_mongo.delete_by_uuid(db_mongo, db_collection, subscriptionId) http_response = JSONResponse(content=retrieved_doc, status_code=200) add_notifications(http_request, http_response, False) + + #CAPIF Core Function Logging Service + try: + response = http_response.body.decode("utf-8") + json_response = {} + json_response.update({"response" : response}) + json_response.update({"status_code" : str(http_response.status_code)}) + ccf_logs(http_request, http_response, "service_as_session_with_qos.json", token_payload.get("sub")) + except TypeError as error: + logging.error(f"Error: {error}") + return http_response 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 b754f8cf..010093ea 100644 --- a/backend/app/app/api/api_v1/endpoints/ue_movement.py +++ b/backend/app/app/api/api_v1/endpoints/ue_movement.py @@ -10,6 +10,7 @@ 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 = {} @@ -24,14 +25,18 @@ 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): + db_mongo = client.fastapi + current_user = self._args[0] supi = self._args[1] - + json_cells = self._args[2] + points = self._args[3] + is_superuser = self._args[4] + active_subscriptions = { "location_reporting" : False, "ue_reachability" : False, @@ -40,57 +45,12 @@ def run(self): } try: - db_mongo = client.fastapi - - #Initiate UE - if exists - UE = crud.ue.get_supi(db=self._db, supi=supi) - if not UE: - logging.warning("UE not found") - threads.pop(f"{supi}") - return - if (UE.owner_id != current_user.id): - logging.warning("Not enough permissions") - threads.pop(f"{supi}") - return - #Insert running UE in the dictionary - - global ues - ues[f"{supi}"] = jsonable_encoder(UE) - ues[f"{supi}"].pop("id") - - if UE.Cell_id != None: - ues[f"{supi}"]["cell_id_hex"] = UE.Cell.cell_id - ues[f"{supi}"]["gnb_id_hex"] = UE.Cell.gNB.gNB_id - else: - ues[f"{supi}"]["cell_id_hex"] = None - ues[f"{supi}"]["gnb_id_hex"] = None - - - #Retrieve paths & points - path = crud.path.get(db=self._db, id=UE.path_id) - if not path: - logging.warning("Path not found") - threads.pop(f"{supi}") - return - if (path.owner_id != current_user.id): - logging.warning("Not enough permissions") - threads.pop(f"{supi}") - return - - 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=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.SequencialTimer(logger=logging.critical) rt = None # global loss_of_connectivity_ack loss_of_connectivity_ack = "FALSE" + ''' =================================================================== 2nd Approach for updating UEs position @@ -125,7 +85,7 @@ def run(self): # find the index of the point where the UE is located for index, point in enumerate(points): - if (UE.latitude == point["latitude"]) and (UE.longitude == point["longitude"]): + if (ues[f"{supi}"]["latitude"] == point["latitude"]) and (ues[f"{supi}"]["longitude"] == point["longitude"]): current_position_index = index # start iterating from this index and keep increasing the moving_position_index... @@ -145,7 +105,7 @@ def run(self): #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") + loss_of_connectivity_sub = crud_mongo.read_by_multiple_pairs(db_mongo, "MonitoringEvent", externalId = ues[f"{supi}"]["external_identifier"], monitoringType = "LOSS_OF_CONNECTIVITY") if loss_of_connectivity_sub: active_subscriptions.update({"loss_of_connectivity" : True}) @@ -183,7 +143,7 @@ def run(self): #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) + qos_sub = crud_mongo.read(db_mongo, 'QoSMonitoring', 'ipv4Addr', ues[f"{supi}"]["ip_address_v4"]) if qos_sub: active_subscriptions.update({"as_session_with_qos" : True}) reporting_freq = qos_sub["qosMonInfo"]["repFreqs"] @@ -218,7 +178,7 @@ def run(self): 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") + ue_reachability_sub = crud_mongo.read_by_multiple_pairs(db_mongo, "MonitoringEvent", externalId = ues[f"{supi}"]["external_identifier"], monitoringType = "UE_REACHABILITY") if ue_reachability_sub: active_subscriptions.update({"ue_reachability" : True}) @@ -251,14 +211,13 @@ def run(self): # logging.warning(f"UE({UE.supi}) with ipv4 {UE.ip_address_v4} handovers to Cell {cell_now.get('id')}, {cell_now.get('description')}") 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=self._db, id=cell_now.get("gNB_id")) - ues[f"{supi}"]["gnb_id_hex"] = gnb.gNB_id + ues[f"{supi}"]["gnb_id_hex"] = cell_now.get('cell_id')[:6] #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") + location_reporting_sub = crud_mongo.read_by_multiple_pairs(db_mongo, "MonitoringEvent", externalId = ues[f"{supi}"]["external_identifier"], monitoringType = "LOCATION_REPORTING") if location_reporting_sub: active_subscriptions.update({"location_reporting" : True}) @@ -309,10 +268,10 @@ def run(self): # logging.info(f'User: {current_user.id} | UE: {supi} | Current location: latitude ={UE.latitude} | longitude = {UE.longitude} | Speed: {UE.speed}' ) - if UE.speed == 'LOW': + if ues[f"{supi}"]["speed"] == 'LOW': # don't skip any points, keep default speed 1m /sec moving_position_index += 1 - elif UE.speed == 'HIGH': + elif ues[f"{supi}"]["speed"] == 'HIGH': # skip 10 points --> 10m / sec moving_position_index += 10 @@ -323,10 +282,13 @@ def run(self): if self._stop_threads: logging.critical("Terminating thread...") - 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"]}) + logging.critical("Updating UE with the latest coordinates and cell in the database (last known position)...") + db = SessionLocal() + UE = crud.ue.get_supi(db, supi) + 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"]}) ues.pop(f"{supi}") - self._db.close() + db.close() if rt is not None: rt.stop() break @@ -403,13 +365,60 @@ def initiate_movement( *, msg: Msg, current_user: models.User = Depends(deps.get_current_active_user), + db: Session = Depends(deps.get_db) ) -> Any: """ Start the loop. """ if msg.supi in threads: raise HTTPException(status_code=409, detail=f"There is a thread already running for this supi:{msg.supi}") - t = BackgroundTasks(args= (current_user, msg.supi, )) + + #Check if UE + UE = crud.ue.get_supi(db=db, supi=msg.supi) + if not UE: + logging.warning("UE not found") + threads.pop(f"{msg.supi}") + return + if (UE.owner_id != current_user.id): + logging.warning("Not enough permissions") + threads.pop(f"{msg.supi}") + return + + #Insert running UE in the dictionary + + global ues + ues[f"{msg.supi}"] = jsonable_encoder(UE) + ues[f"{msg.supi}"].pop("id") + + if UE.Cell_id != None: + ues[f"{msg.supi}"]["cell_id_hex"] = UE.Cell.cell_id + ues[f"{msg.supi}"]["gnb_id_hex"] = UE.Cell.gNB.gNB_id + else: + ues[f"{msg.supi}"]["cell_id_hex"] = None + ues[f"{msg.supi}"]["gnb_id_hex"] = None + + + #Retrieve paths & points + path = crud.path.get(db=db, id=UE.path_id) + if not path: + logging.warning("Path not found") + threads.pop(f"{msg.supi}") + return + if (path.owner_id != current_user.id): + logging.warning("Not enough permissions") + threads.pop(f"{msg.supi}") + return + + points = crud.points.get_points(db=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) + json_cells = jsonable_encoder(Cells) + + is_superuser = crud.user.is_superuser(current_user) + + t = BackgroundTasks(args= (current_user, msg.supi, json_cells, points, is_superuser)) threads[f"{msg.supi}"] = {} threads[f"{msg.supi}"][f"{current_user.id}"] = t t.start() diff --git a/backend/app/app/api/api_v1/endpoints/utils.py b/backend/app/app/api/api_v1/endpoints/utils.py index 70750b68..dffb47ee 100644 --- a/backend/app/app/api/api_v1/endpoints/utils.py +++ b/backend/app/app/api/api_v1/endpoints/utils.py @@ -12,6 +12,63 @@ from pydantic import BaseModel from app.api.api_v1.endpoints.paths import get_random_point from app.api.api_v1.endpoints.ue_movement import retrieve_ue_state +from evolved5g.sdk import CAPIFLogger + +#Create CAPIF Logger object +def ccf_logs(input_request: Request, output_response: dict, service_api_description: str, invoker_id: str): + + try: + capif_logger = CAPIFLogger(certificates_folder="app/core/certificates", + capif_host="capifcore", + capif_https_port="443" + ) + + log_entries = [] + service_description = capif_logger.get_capif_service_description(capif_service_api_description_json_full_path= + f"app/core/certificates/CAPIF_{service_api_description}") + + api_id = service_description["apiId"] + + endpoint = input_request.url.path + if endpoint.find('monitoring') != -1: + resource = "Monitoring_Event_API" + endpoint = "/nef/api/v1/3gpp-monitoring-event/" + elif endpoint.find('session-with-qos') != -1: + resource = "AsSession_With_QoS_API" + endpoint = "/nef/api/v1/3gpp-as-session-with-qos/" + + #Request body check and trim + if(input_request.method == 'POST') or (input_request.method == 'PUT'): + req_body = input_request._body.decode("utf-8").replace('\n', '') + req_body = req_body.replace(' ', '') + req_body = json.loads(req_body) + else: + req_body = " " + + url_string = "https://" + input_request.url.hostname + ":4443" + endpoint + + log_entry = CAPIFLogger.LogEntry( + apiId = api_id, + apiVersion="v1", + apiName=endpoint, + resourceName=resource, + uri=url_string, + protocol="HTTP_1_1", + invocationTime= datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + invocationLatency=10, + operation=input_request.method, + result= output_response.get("status_code"), + inputParameters=req_body, + outputParameters=output_response.get("response") + ) + + log_entries.append(log_entry) + api_invoker_id = invoker_id + capif_logger.save_log(api_invoker_id,log_entries) + except Exception as ex: + logging.critical(ex) + logging.critical("Potential cause of failure: CAPIF Core Function is not deployed or unreachable") + #List holding notifications from event_notifications = [] diff --git a/backend/app/app/backend_pre_start.py b/backend/app/app/backend_pre_start.py index 6912610b..325a9151 100644 --- a/backend/app/app/backend_pre_start.py +++ b/backend/app/app/backend_pre_start.py @@ -52,15 +52,20 @@ def capif_nef_connector(): capif_connector.publish_services(service_api_description_json_full_path="app/core/capif_files/service_monitoring_event.json") capif_connector.publish_services(service_api_description_json_full_path="app/core/capif_files/service_as_session_with_qos.json") + return True except requests.exceptions.HTTPError as err: if err.response.status_code == 409: logger.error(f'"Http Error:", {err.response.json()}') + return False except requests.exceptions.ConnectionError as err: logger.error(f'"Error Connecting:", {err}') + return False except requests.exceptions.Timeout as err: logger.error(f'"Timeout Error:", {err}') + return False except requests.exceptions.RequestException as err: logger.error(f'"Error:", {err}') + return False def main() -> None: @@ -68,8 +73,10 @@ def main() -> None: init() logger.info("Service finished initializing") logger.info("Trying to connect with CAPIF Core Function") - capif_nef_connector() - logger.info("Successfully onboard to CAPIF Core Function") + if capif_nef_connector(): + logger.info("Successfully onboard NEF in the CAPIF Core Function") + else: + logger.info("Failed to onboard NEF in the CAPIF Core Function") if __name__ == "__main__": diff --git a/backend/app/app/static/js/dashboard-cells.js b/backend/app/app/static/js/dashboard-cells.js index 235fe794..dcf1089d 100644 --- a/backend/app/app/static/js/dashboard-cells.js +++ b/backend/app/app/static/js/dashboard-cells.js @@ -566,10 +566,16 @@ function ui_initialize_edit_cell_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -580,7 +586,8 @@ function ui_initialize_edit_cell_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = { @@ -602,10 +609,16 @@ function ui_initialize_add_cell_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -616,7 +629,8 @@ function ui_initialize_add_cell_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = { diff --git a/backend/app/app/static/js/dashboard-paths.js b/backend/app/app/static/js/dashboard-paths.js index 65e7a9cd..eee03012 100644 --- a/backend/app/app/static/js/dashboard-paths.js +++ b/backend/app/app/static/js/dashboard-paths.js @@ -562,10 +562,16 @@ function ui_initialize_edit_path_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -576,7 +582,8 @@ function ui_initialize_edit_path_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = { @@ -597,10 +604,16 @@ function ui_initialize_add_path_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -611,7 +624,8 @@ function ui_initialize_add_path_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = { diff --git a/backend/app/app/static/js/dashboard-ues.js b/backend/app/app/static/js/dashboard-ues.js index 05359eeb..27b18cd9 100644 --- a/backend/app/app/static/js/dashboard-ues.js +++ b/backend/app/app/static/js/dashboard-ues.js @@ -614,10 +614,16 @@ function ui_initialize_edit_UE_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -628,7 +634,8 @@ function ui_initialize_edit_UE_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = { @@ -652,10 +659,16 @@ function ui_initialize_add_UE_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -666,7 +679,8 @@ function ui_initialize_add_UE_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = { diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js index 04547180..a870d94c 100644 --- a/backend/app/app/static/js/map.js +++ b/backend/app/app/static/js/map.js @@ -274,10 +274,16 @@ function ui_initialize_map() { var mbAttr = 'Map data © OpenStreetMap contributors, ' + 'Imagery © Mapbox', - mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; + mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiZXhhbXBsZXMiLCJhIjoiY2p0MG01MXRqMW45cjQzb2R6b2ptc3J4MSJ9.zA2W0IkI0c6KaAhJfk9bWg'; + + var osAttr = '© OpenStreetMap', + osUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), - streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); + streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + osm = L.tileLayer(osUrl, { tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization @@ -289,7 +295,8 @@ function ui_initialize_map() { var baseLayers = { "Grayscale": grayscale, - "Streets": streets + "Streets": streets, + 'OpenStreetMap': osm }; var overlays = {