Skip to content

Commit

Permalink
[mock_uss] Reorganize mock_uss flight functionality (#294)
Browse files Browse the repository at this point in the history
Reorganize mock_uss flight functionality
  • Loading branch information
BenjaminPelletier authored Oct 31, 2023
1 parent 7fb0ef7 commit 345ff7c
Show file tree
Hide file tree
Showing 21 changed files with 131 additions and 104 deletions.
5 changes: 3 additions & 2 deletions monitoring/mock_uss/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ The available functionality sets are:
* [`msgsigning`](msgsigning): [IETF HTTP Message Signatures](https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/)
* [`riddp`](riddp): Remote ID Display Provider
* [`ridsp`](ridsp): Remote ID Service Provider
* [`scdsc`](scdsc): ASTM F3548 strategic coordinator
* `scdsc`: Combination of [ASTM F3548-21](f3548v21) strategic conflict detection and [scd flight injection](scd_injection)
* [`flight_planning`](flight_planning): Exposes [InterUSS flight_planning automated testing API](https://github.com/interuss/automated_testing_interfaces/tree/main/flight_planning)
* [`tracer`](tracer): Interoperability ecosystem tracer logger
* [`interaction_logging`](interaction_logging): Enables logging of the [interuss](https://github.com/astm-utm/Protocol/blob/master/utm.yaml) interactions between mock_uss and other uss participants
* [`interaction_logging`](interaction_logging): Enables logging of interactions between mock_uss and other uss participants


## Local deployment
Expand Down
4 changes: 2 additions & 2 deletions monitoring/mock_uss/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ def require_config_value(config_key: str) -> None:

if SERVICE_SCDSC in webapp.config[config.KEY_SERVICES]:
enabled_services.add(SERVICE_SCDSC)
from monitoring.mock_uss import scdsc
from monitoring.mock_uss.scdsc import routes as scdsc_routes
from monitoring.mock_uss.f3548v21 import routes_scd
from monitoring.mock_uss.scd_injection import routes as scd_injection_routes

if SERVICE_MESSAGESIGNING in webapp.config[config.KEY_SERVICES]:
enabled_services.add(SERVICE_MESSAGESIGNING)
Expand Down
3 changes: 3 additions & 0 deletions monitoring/mock_uss/f3548v21/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# mock_uss: ASTM F3548-21

[ASTM F3548-21](http://astm.org/f3548-21.html) standardizes UTM interoperability between USSs to achieve strategic coordination and communicate constraints. This folder enables [mock_uss](..) to comply with the Strategic Conflict Detection requirements from that standard.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,30 @@
from typing import Optional, List, Callable

import arrow

from monitoring.uss_qualifier.resources.overrides import apply_overrides
from uas_standards.astm.f3548.v21 import api as f3548_v21
from uas_standards.astm.f3548.v21.api import OperationalIntentDetails, OperationalIntent
from uas_standards.astm.f3548.v21.constants import OiMaxVertices, OiMaxPlanHorizonDays
from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api

from monitoring.mock_uss.scdsc.database import FlightRecord
from monitoring.mock_uss.flights.database import FlightRecord
from monitoring.monitorlib.geotemporal import Volume4DCollection
from monitoring.monitorlib.locality import Locality
from monitoring.monitorlib.uspace import problems_with_flight_authorisation
from uas_standards.interuss.automated_testing.scd.v1.api import OperationalIntentState


class PlanningError(Exception):
pass


def validate_request(req_body: scd_api.InjectFlightRequest, locality: Locality) -> None:
def validate_request(req_body: scd_api.InjectFlightRequest) -> None:
"""Raise a PlannerError if the request is not valid.
Args:
req_body: Information about the requested flight.
locality: Jurisdictional requirements which the mock_uss should follow.
"""
if locality.is_uspace_applicable():
# Validate flight authorisation
problems = problems_with_flight_authorisation(req_body.flight_authorisation)
if problems:
raise PlanningError(", ".join(problems))

# Validate max number of vertices
nb_vertices = 0
for volume in (
Expand Down Expand Up @@ -219,3 +215,23 @@ def op_intent_transition_valid(

else:
return False


def op_intent_from_flightrecord(flight: FlightRecord, method: str) -> OperationalIntent:
ref = flight.op_intent.reference
details = OperationalIntentDetails(
volumes=flight.op_intent.details.volumes,
off_nominal_volumes=flight.op_intent.details.off_nominal_volumes,
priority=flight.op_intent.details.priority,
)
op_intent = OperationalIntent(reference=ref, details=details)
if flight.mod_op_sharing_behavior:
mod_op_sharing_behavior = flight.mod_op_sharing_behavior
if mod_op_sharing_behavior.modify_sharing_methods is not None:
if method not in mod_op_sharing_behavior.modify_sharing_methods:
return op_intent
op_intent = apply_overrides(
op_intent, mod_op_sharing_behavior.modify_fields, parse_result=False
)

return op_intent
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import json

import flask
from implicitdict import ImplicitDict

from monitoring.mock_uss.f3548v21.flight_planning import op_intent_from_flightrecord
from monitoring.monitorlib import scd
from monitoring.mock_uss import webapp
from monitoring.mock_uss.auth import requires_scope
from monitoring.mock_uss.scdsc.database import db
from monitoring.mock_uss.scdsc.database import FlightRecord
from monitoring.uss_qualifier.resources.overrides import (
apply_overrides,
)
from monitoring.mock_uss.flights.database import db
from uas_standards.astm.f3548.v21.api import (
ErrorResponse,
OperationalIntent,
OperationalIntentDetails,
GetOperationalIntentDetailsResponse,
)

Expand Down Expand Up @@ -51,26 +44,6 @@ def scdsc_get_operational_intent_details(entityid: str):
return flask.jsonify(response), 200


def op_intent_from_flightrecord(flight: FlightRecord, method: str) -> OperationalIntent:
ref = flight.op_intent.reference
details = OperationalIntentDetails(
volumes=flight.op_intent.details.volumes,
off_nominal_volumes=flight.op_intent.details.off_nominal_volumes,
priority=flight.op_intent.details.priority,
)
op_intent = OperationalIntent(reference=ref, details=details)
if flight.mod_op_sharing_behavior:
mod_op_sharing_behavior = flight.mod_op_sharing_behavior
if mod_op_sharing_behavior.modify_sharing_methods is not None:
if method not in mod_op_sharing_behavior.modify_sharing_methods:
return op_intent
op_intent = apply_overrides(
op_intent, mod_op_sharing_behavior.modify_fields, parse_result=False
)

return op_intent


@webapp.route("/mock/scd/uss/v1/operational_intents", methods=["POST"])
@requires_scope(scd.SCOPE_SC)
def scdsc_notify_operational_intent_details_changed():
Expand Down
3 changes: 3 additions & 0 deletions monitoring/mock_uss/flight_planning/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# mock_uss: flight_planner

This folder contains materials implementing [InterUSS's flight_planning automated testing interface](https://github.com/interuss/automated_testing_interfaces/tree/main/flight_planning) by [mock_uss](..).
2 changes: 1 addition & 1 deletion monitoring/mock_uss/flight_planning/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from implicitdict import ImplicitDict
from loguru import logger

from monitoring.mock_uss.scdsc.routes_injection import (
from monitoring.mock_uss.scd_injection.routes_injection import (
inject_flight,
lock_flight,
release_flight_lock,
Expand Down
3 changes: 3 additions & 0 deletions monitoring/mock_uss/flights/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# mock_uss: flights

This folder contains materials related to generic handling of user-requested flights by [mock_uss](..).
Empty file.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from datetime import timedelta
from typing import Dict, Optional

from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo
Expand All @@ -11,6 +12,8 @@
MockUssFlightBehavior,
)

DEADLOCK_TIMEOUT = timedelta(seconds=5)


class FlightRecord(ImplicitDict):
"""Representation of a flight in a USS"""
Expand Down
47 changes: 47 additions & 0 deletions monitoring/mock_uss/flights/planning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import time
from datetime import datetime
from typing import Callable

from monitoring.mock_uss.flights.database import FlightRecord, db, DEADLOCK_TIMEOUT


def lock_flight(flight_id: str, log: Callable[[str], None]) -> FlightRecord:
# If this is a change to an existing flight, acquire lock to that flight
log(f"Acquiring lock for flight {flight_id}")
deadline = datetime.utcnow() + DEADLOCK_TIMEOUT
while True:
with db as tx:
if flight_id in tx.flights:
# This is an existing flight being modified
existing_flight = tx.flights[flight_id]
if existing_flight and not existing_flight.locked:
log("Existing flight locked for update")
existing_flight.locked = True
break
else:
log("Request is for a new flight (lock established)")
tx.flights[flight_id] = None
existing_flight = None
break
# We found an existing flight but it was locked; wait for it to become
# available
time.sleep(0.5)

if datetime.utcnow() > deadline:
raise RuntimeError(
f"Deadlock in inject_flight while attempting to gain access to flight {flight_id}"
)
return existing_flight


def release_flight_lock(flight_id: str, log: Callable[[str], None]) -> None:
with db as tx:
if flight_id in tx.flights:
if tx.flights[flight_id]:
# FlightRecord was a true existing flight
log(f"Releasing lock on existing flight_id {flight_id}")
tx.flights[flight_id].locked = False
else:
# FlightRecord was just a placeholder for a new flight
log(f"Releasing placeholder for existing flight_id {flight_id}")
del tx.flights[flight_id]
3 changes: 3 additions & 0 deletions monitoring/mock_uss/scd_injection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# mock_uss: scd_injection

This folder contains material related to the deprecated [InterUSS scd automated testing interface](https://github.com/interuss/automated_testing_interfaces/tree/main/scd).
Empty file.
6 changes: 6 additions & 0 deletions monitoring/mock_uss/scd_injection/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from monitoring.mock_uss import webapp


@webapp.route("/scdsc/status")
def scdsc_status():
return "scd flight injection API ok"
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
import traceback
from datetime import datetime, timedelta
import time
from typing import List, Tuple, Callable, Optional
from typing import List, Tuple, Optional
import uuid

import flask
from implicitdict import ImplicitDict, StringBasedDateTime
from loguru import logger
import requests.exceptions

from monitoring.mock_uss.flights.planning import lock_flight, release_flight_lock
from monitoring.mock_uss.f3548v21 import utm_client
from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo
from uas_standards.astm.f3548.v21 import api
from uas_standards.astm.f3548.v21.api import (
Expand All @@ -32,19 +34,20 @@
OperationalIntentState,
)

from monitoring.mock_uss import webapp, require_config_value
from monitoring.mock_uss import webapp, require_config_value, uspace
from monitoring.mock_uss.auth import requires_scope
from monitoring.mock_uss.config import KEY_BASE_URL
from monitoring.mock_uss.dynamic_configuration.configuration import get_locality
from monitoring.mock_uss.scdsc import database, utm_client
from monitoring.mock_uss.scdsc.database import db, FlightRecord
from monitoring.mock_uss.scdsc.flight_planning import (
from monitoring.mock_uss.flights import database
from monitoring.mock_uss.flights.database import db, FlightRecord
from monitoring.mock_uss.f3548v21.flight_planning import (
validate_request,
check_for_disallowed_conflicts,
PlanningError,
op_intent_from_flightrecord,
op_intent_transition_valid,
)
from monitoring.mock_uss.scdsc.routes_scdsc import op_intent_from_flightrecord
import monitoring.mock_uss.uspace.flight_auth
from monitoring.monitorlib import versioning
from monitoring.monitorlib.clients import scd as scd_client
from monitoring.monitorlib.fetch import QueryError
Expand Down Expand Up @@ -188,48 +191,6 @@ def _mock_uss_flight_behavior_in_req(
return None


def lock_flight(flight_id: str, log: Callable[[str], None]) -> FlightRecord:
# If this is a change to an existing flight, acquire lock to that flight
log(f"Acquiring lock for flight {flight_id}")
deadline = datetime.utcnow() + DEADLOCK_TIMEOUT
while True:
with db as tx:
if flight_id in tx.flights:
# This is an existing flight being modified
existing_flight = tx.flights[flight_id]
if existing_flight and not existing_flight.locked:
log("Existing flight locked for update")
existing_flight.locked = True
break
else:
log("Request is for a new flight (lock established)")
tx.flights[flight_id] = None
existing_flight = None
break
# We found an existing flight but it was locked; wait for it to become
# available
time.sleep(0.5)

if datetime.utcnow() > deadline:
raise RuntimeError(
f"Deadlock in inject_flight while attempting to gain access to flight {flight_id}"
)
return existing_flight


def release_flight_lock(flight_id: str, log: Callable[[str], None]) -> None:
with db as tx:
if flight_id in tx.flights:
if tx.flights[flight_id]:
# FlightRecord was a true existing flight
log(f"Releasing lock on existing flight_id {flight_id}")
tx.flights[flight_id].locked = False
else:
# FlightRecord was just a placeholder for a new flight
log(f"Releasing placeholder for existing flight_id {flight_id}")
del tx.flights[flight_id]


def inject_flight(
flight_id: str,
req_body: MockUSSInjectFlightRequest,
Expand All @@ -244,7 +205,9 @@ def log(msg: str):
# Validate request
log("Validating request")
try:
validate_request(req_body, locality)
if locality.is_uspace_applicable():
uspace.flight_auth.validate_request(req_body)
validate_request(req_body)
except PlanningError as e:
return (
InjectFlightResponse(
Expand Down
1 change: 0 additions & 1 deletion monitoring/mock_uss/scdsc/README.md

This file was deleted.

10 changes: 0 additions & 10 deletions monitoring/mock_uss/scdsc/routes.py

This file was deleted.

3 changes: 3 additions & 0 deletions monitoring/mock_uss/uspace/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# mock_uss: uspace

This folder contains materials allowing mock_uss to emulate behaviors required in U-space.
Empty file.
14 changes: 14 additions & 0 deletions monitoring/mock_uss/uspace/flight_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from monitoring.mock_uss.f3548v21.flight_planning import PlanningError
from monitoring.monitorlib.uspace import problems_with_flight_authorisation
from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api


def validate_request(req_body: scd_api.InjectFlightRequest) -> None:
"""Raise a PlannerError if the request is not valid.
Args:
req_body: Information about the requested flight.
"""
problems = problems_with_flight_authorisation(req_body.flight_authorisation)
if problems:
raise PlanningError(", ".join(problems))

0 comments on commit 345ff7c

Please sign in to comment.