Skip to content

Commit

Permalink
[mock_uss] Modify sharing behavior in mock_uss on injection (#269)
Browse files Browse the repository at this point in the history
* Modify sharing behavior in mock_uss on injection

* Added additional_fields to InjectFlightRequest

* Fix format

* Added documentation for MockUssFlightBehavior

* Fix format

* Implement PR suggestions

---------

Co-authored-by: Benjamin Pelletier <[email protected]>
  • Loading branch information
punamverma and BenjaminPelletier authored Oct 24, 2023
1 parent 5a685db commit 0b6beea
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 114 deletions.
7 changes: 4 additions & 3 deletions monitoring/mock_uss/scdsc/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@

from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo
from monitoring.monitorlib.multiprocessing import SynchronizedValue
from uas_standards.interuss.automated_testing.scd.v1 import api as scd_injection_api
from implicitdict import ImplicitDict
from uas_standards.astm.f3548.v21.api import (
OperationalIntentReference,
OperationalIntent,
)
from monitoring.monitorlib.clients.mock_uss.mock_uss_scd_injection_api import (
MockUssFlightBehavior,
)


class FlightRecord(ImplicitDict):
"""Representation of a flight in a USS"""

flight_info: FlightInfo
op_intent: OperationalIntent

mod_op_sharing_behavior: Optional[MockUssFlightBehavior] = None
locked: bool = False


Expand Down
67 changes: 43 additions & 24 deletions monitoring/mock_uss/scdsc/routes_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import traceback
from datetime import datetime, timedelta
import time
from typing import List, Tuple
from typing import List, Tuple, Optional
import uuid

import flask
Expand All @@ -20,7 +20,6 @@
OperationalIntentDetails,
)
from uas_standards.interuss.automated_testing.scd.v1.api import (
InjectFlightRequest,
InjectFlightResponse,
InjectFlightResponseResult,
DeleteFlightResponse,
Expand Down Expand Up @@ -55,7 +54,10 @@
from monitoring.monitorlib.scd_automated_testing.scd_injection_api import (
SCOPE_SCD_QUALIFIER_INJECT,
)

from monitoring.monitorlib.clients.mock_uss.mock_uss_scd_injection_api import (
MockUSSInjectFlightRequest,
MockUssFlightBehavior,
)

require_config_value(KEY_BASE_URL)

Expand Down Expand Up @@ -86,7 +88,9 @@ def query_operational_intents(
for op_intent_ref in op_intent_refs:
if op_intent_ref.id in own_flights:
# This is our own flight
result.append(op_intent_from_flightrecord(own_flights[op_intent_ref.id]))
result.append(
op_intent_from_flightrecord(own_flights[op_intent_ref.id], "GET")
)
elif (
op_intent_ref.id in tx.cached_operations
and tx.cached_operations[op_intent_ref.id].reference.version
Expand Down Expand Up @@ -159,15 +163,26 @@ def scdsc_inject_flight(flight_id: str) -> Tuple[str, int]:
json = flask.request.json
if json is None:
raise ValueError("Request did not contain a JSON payload")
req_body: InjectFlightRequest = ImplicitDict.parse(json, InjectFlightRequest)
req_body = ImplicitDict.parse(json, MockUSSInjectFlightRequest)
except ValueError as e:
msg = "Create flight {} unable to parse JSON: {}".format(flight_id, e)
return msg, 400
json, code = inject_flight(flight_id, req_body)
return flask.jsonify(json), code


def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict, int]:
def _mock_uss_flight_behavior_in_req(
req_body: MockUSSInjectFlightRequest,
) -> Optional[MockUssFlightBehavior]:
if "behavior" in req_body:
return req_body.behavior
else:
return None


def inject_flight(
flight_id: str, req_body: MockUSSInjectFlightRequest
) -> Tuple[dict, int]:
pid = os.getpid()
locality = get_locality()

Expand Down Expand Up @@ -206,8 +221,11 @@ def log(msg: str):
# We found an existing flight but it was locked; wait for it to become
# available
time.sleep(0.5)

log(
f"Waiting for flight lock resolution; now: {datetime.utcnow()} deadline: {deadline}"
)
if datetime.utcnow() > deadline:
log(f"Deadlock (now: {datetime.utcnow()}, deadline: {deadline})")
raise RuntimeError(
f"Deadlock in inject_flight while attempting to gain access to flight {flight_id}"
)
Expand Down Expand Up @@ -300,10 +318,20 @@ def log(msg: str):
# Notify subscribers
subscriber_list = ", ".join(s.uss_base_url for s in result.subscribers)
step_name = f"notifying subscribers {{{subscriber_list}}}"
operational_intent = OperationalIntent(
op_intent = OperationalIntent(
reference=result.operational_intent_reference,
details=req_body.operational_intent,
details=OperationalIntentDetails(
volumes=req_body.operational_intent.volumes,
off_nominal_volumes=req_body.operational_intent.off_nominal_volumes,
priority=req_body.operational_intent.priority,
),
)
record = database.FlightRecord(
op_intent=op_intent,
flight_info=FlightInfo.from_scd_inject_flight_request(req_body),
mod_op_sharing_behavior=_mock_uss_flight_behavior_in_req(req_body),
)
operational_intent = op_intent_from_flightrecord(record, "POST")
for subscriber in result.subscribers:
if subscriber.uss_base_url == base_url:
# Do not notify ourselves
Expand All @@ -322,18 +350,6 @@ def log(msg: str):
# Store flight in database
step_name = "storing flight in database"
log("Storing flight in database")
op_intent = OperationalIntent(
reference=result.operational_intent_reference,
details=OperationalIntentDetails(
volumes=req_body.operational_intent.volumes,
off_nominal_volumes=req_body.operational_intent.off_nominal_volumes,
priority=req_body.operational_intent.priority,
),
)
record = database.FlightRecord(
op_intent=op_intent,
flight_info=FlightInfo.from_scd_inject_flight_request(req_body),
)
with db as tx:
tx.flights[flight_id] = record

Expand Down Expand Up @@ -412,9 +428,10 @@ def delete_flight(flight_id) -> Tuple[dict, int]:
break
# There is a race condition with another handler to create or modify the requested flight; wait for that to resolve
time.sleep(0.5)

if datetime.utcnow() > deadline:
logger.debug(f"[delete_flight/{pid}:{flight_id}] Deadlock")
logger.error(
f"[delete_flight/{pid}:{flight_id}] Deadlock (now: {datetime.utcnow()}, deadline: {deadline})"
)
raise RuntimeError(
f"Deadlock in delete_flight while attempting to gain access to flight {flight_id}"
)
Expand Down Expand Up @@ -581,8 +598,10 @@ def make_result(success: bool, msg: str) -> ClearAreaResponse:
if not pending_flights:
break
time.sleep(0.5)

if datetime.utcnow() > deadline:
logger.error(
f"[clear_area] Deadlock (now: {datetime.utcnow()}, deadline: {deadline})"
)
raise RuntimeError(
f"Deadlock in clear_area while attempting to gain access to flight(s) {', '.join(pending_flights)}"
)
Expand Down
39 changes: 27 additions & 12 deletions monitoring/mock_uss/scdsc/routes_scdsc.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import flask
import json

import flask
from implicitdict import ImplicitDict
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, FlightRecord
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 uas_standards.astm.f3548.v21.api import (
GetOperationalIntentDetailsResponse,
ErrorResponse,
OperationalIntent,
OperationalIntentDetails,
GetOperationalIntentDetailsResponse,
)


Expand Down Expand Up @@ -40,20 +46,29 @@ def scdsc_get_operational_intent_details(entityid: str):

# Return nominal response with details
response = GetOperationalIntentDetailsResponse(
operational_intent=op_intent_from_flightrecord(flight),
operational_intent=op_intent_from_flightrecord(flight, "GET")
)
return flask.jsonify(response), 200


def op_intent_from_flightrecord(flight: FlightRecord) -> OperationalIntent:
return OperationalIntent(
reference=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,
),
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"])
Expand Down
6 changes: 5 additions & 1 deletion monitoring/monitorlib/clients/flight_planning/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class FlightPlannerClient(ABC):

@abstractmethod
def try_plan_flight(
self, flight_info: FlightInfo, execution_style: ExecutionStyle
self,
flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
"""Instruct the USS to emulate a normal user trying to plan the described flight.
Expand All @@ -43,6 +46,7 @@ def try_update_flight(
flight_id: FlightID,
updated_flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
"""Instruct the USS to emulate a normal user trying to update the specified flight as described.
Expand Down
21 changes: 16 additions & 5 deletions monitoring/monitorlib/clients/flight_planning/client_scd.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import uuid
from typing import Dict

from typing import Dict, Optional
from implicitdict import ImplicitDict
from monitoring.monitorlib.clients.flight_planning.client import (
FlightPlannerClient,
Expand Down Expand Up @@ -45,6 +44,7 @@ def _inject(
flight_id: FlightID,
flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
if execution_style != ExecutionStyle.IfAllowed:
raise PlanningActivityError(
Expand Down Expand Up @@ -107,6 +107,9 @@ def _inject(
flight_info.uspace_flight_authorisation, scd_api.FlightAuthorisationData
)
req = scd_api.InjectFlightRequest(**kwargs)
if additional_fields:
for k, v in additional_fields.items():
req[k] = v

op = scd_api.OPERATIONS[scd_api.OperationID.InjectFlight]
url = op.path.format(flight_id=flight_id)
Expand Down Expand Up @@ -156,17 +159,25 @@ def _inject(
return response

def try_plan_flight(
self, flight_info: FlightInfo, execution_style: ExecutionStyle
self,
flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
return self._inject(str(uuid.uuid4()), flight_info, execution_style)
return self._inject(
str(uuid.uuid4()), flight_info, execution_style, additional_fields
)

def try_update_flight(
self,
flight_id: FlightID,
updated_flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
return self._inject(flight_id, updated_flight_info, execution_style)
return self._inject(
flight_id, updated_flight_info, execution_style, additional_fields
)

def try_end_flight(
self, flight_id: FlightID, execution_style: ExecutionStyle
Expand Down
20 changes: 17 additions & 3 deletions monitoring/monitorlib/clients/flight_planning/client_v1.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from typing import Optional

from implicitdict import ImplicitDict
from monitoring.monitorlib.clients.flight_planning.client import (
Expand Down Expand Up @@ -36,13 +37,17 @@ def _inject(
flight_plan_id: FlightID,
flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
flight_plan = ImplicitDict.parse(flight_info, api.FlightPlan)
req = api.UpsertFlightPlanRequest(
flight_plan=flight_plan,
execution_style=execution_style,
request_id=str(uuid.uuid4()),
)
if additional_fields:
for k, v in additional_fields.items():
req[k] = v

op = api.OPERATIONS[api.OperationID.UpsertFlightPlan]
url = op.path.format(flight_plan_id=flight_plan_id)
Expand All @@ -69,20 +74,29 @@ def _inject(
activity_result=resp.planning_result,
flight_plan_status=resp.flight_plan_status,
)

return response

def try_plan_flight(
self, flight_info: FlightInfo, execution_style: ExecutionStyle
self,
flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
return self._inject(str(uuid.uuid4()), flight_info, execution_style)
return self._inject(
str(uuid.uuid4()), flight_info, execution_style, additional_fields
)

def try_update_flight(
self,
flight_id: FlightID,
updated_flight_info: FlightInfo,
execution_style: ExecutionStyle,
additional_fields: Optional[dict] = None,
) -> PlanningActivityResponse:
return self._inject(flight_id, updated_flight_info, execution_style)
return self._inject(
flight_id, updated_flight_info, execution_style, additional_fields
)

def try_end_flight(
self, flight_id: FlightID, execution_style: ExecutionStyle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from implicitdict import ImplicitDict
from typing import List, Optional
from uas_standards.interuss.automated_testing.scd.v1.api import InjectFlightRequest


class MockUssFlightBehavior(ImplicitDict):
"""
Interface for modifying mock_uss flight sharing behavior with other USSes
Specify the http method and the fields to modify for those requests
Eg -
{"modify_sharing_methods"=["GET", "POST"],
"modify_fields"={
"operational_intent_reference": {"state": "Flying"},
"operational_intent_details": {"priority": -1},
}
}
"""

modify_sharing_methods: List[str]
""" list of intent sharing http methods GET and POST to be modified"""

modify_fields: dict
"""dict that specifies the values for the fields to be overriden in the operational_intent while sharing"""


class MockUSSInjectFlightRequest(InjectFlightRequest):
"""InjectFlightRequest sent to mock_uss, which looks for the optional additional fields below."""

behavior: Optional[MockUssFlightBehavior]
Loading

0 comments on commit 0b6beea

Please sign in to comment.