Skip to content

Commit

Permalink
[monitorlib] Clean SCD injection API (#227)
Browse files Browse the repository at this point in the history
Clean SCD injection API
  • Loading branch information
BenjaminPelletier authored Oct 5, 2023
1 parent 1d3fd39 commit 0d773a2
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 315 deletions.
4 changes: 1 addition & 3 deletions monitoring/atproxy/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

from monitoring.monitorlib.rid_automated_testing import injection_api
from implicitdict import ImplicitDict
from monitoring.monitorlib.scd_automated_testing.scd_injection_api import \
InjectFlightRequest
from uas_standards.interuss.automated_testing.scd.v1.api import (
ClearAreaRequest,
ClearAreaRequest, InjectFlightRequest,
)


Expand Down
5 changes: 1 addition & 4 deletions monitoring/mock_uss/atproxy_client/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import requests
from uas_standards.interuss.automated_testing.scd.v1.api import (
ClearAreaRequest,
InjectFlightRequest,
)

from implicitdict import ImplicitDict
Expand All @@ -32,10 +33,6 @@
delete_flight,
clear_area,
)
from monitoring.monitorlib.scd_automated_testing.scd_injection_api import (
InjectFlightRequest,
)

from monitoring.monitorlib import fetch

TASK_POLL_ATPROXY = "poll atproxy"
Expand Down
2 changes: 1 addition & 1 deletion monitoring/mock_uss/scdsc/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Dict, Optional

from monitoring.monitorlib.multiprocessing import SynchronizedValue
from monitoring.monitorlib.scd_automated_testing import scd_injection_api
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,
Expand Down
70 changes: 42 additions & 28 deletions monitoring/mock_uss/scdsc/routes_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from monitoring.mock_uss.scdsc.routes_scdsc import op_intent_from_flightrecord
from monitoring.monitorlib.geo import Polygon
from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection
from uas_standards.astm.f3548.v21 import api
from uas_standards.astm.f3548.v21.api import (
OperationalIntent,
PutOperationalIntentDetailsParameters,
Expand All @@ -28,20 +29,21 @@
from monitoring.monitorlib import scd, versioning
from monitoring.monitorlib.clients import scd as scd_client
from monitoring.monitorlib.fetch import QueryError
from monitoring.monitorlib.scd import op_intent_transition_valid
from monitoring.monitorlib.scd_automated_testing.scd_injection_api import (
from uas_standards.interuss.automated_testing.scd.v1.api import (
InjectFlightRequest,
InjectFlightResponse,
SCOPE_SCD_QUALIFIER_INJECT,
InjectFlightResult,
InjectFlightResponseResult,
DeleteFlightResponse,
DeleteFlightResult,
DeleteFlightResponseResult,
ClearAreaRequest,
ClearAreaOutcome,
ClearAreaResponse,
Capability,
CapabilitiesResponse,
)
from monitoring.monitorlib.scd_automated_testing.scd_injection_api import (
SCOPE_SCD_QUALIFIER_INJECT,
)
from implicitdict import ImplicitDict, StringBasedDateTime
from monitoring.mock_uss import webapp, require_config_value
from monitoring.mock_uss.auth import requires_scope
Expand All @@ -63,7 +65,7 @@ def _make_stacktrace(e) -> str:


def query_operational_intents(
area_of_interest: scd.Volume4D,
area_of_interest: api.Volume4D,
) -> List[OperationalIntent]:
"""Retrieve a complete set of operational intents in an area, including details.
Expand Down Expand Up @@ -187,7 +189,8 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
if problems:
return (
InjectFlightResponse(
result=InjectFlightResult.Rejected, notes=", ".join(problems)
result=InjectFlightResponseResult.Rejected,
notes=", ".join(problems),
),
200,
)
Expand All @@ -206,14 +209,14 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
if nb_vertices > OiMaxVertices:
return (
InjectFlightResponse(
result=InjectFlightResult.Rejected,
result=InjectFlightResponseResult.Rejected,
notes=f"Too many vertices across volumes of operational intent (max OiMaxVertices={OiMaxVertices})",
),
200,
)

# Validate max planning horizon for creation
start_time = Volume4DCollection.from_f3548v21(
start_time = Volume4DCollection.from_interuss_scd_api(
req_body.operational_intent.volumes
).time_start.datetime
time_delta = start_time - datetime.now(tz=start_time.tzinfo)
Expand All @@ -223,7 +226,7 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
):
return (
InjectFlightResponse(
result=InjectFlightResult.Rejected,
result=InjectFlightResponseResult.Rejected,
notes=f"Operational intent to plan is too far away in time (max OiMaxPlanHorizonDays={OiMaxPlanHorizonDays})",
),
200,
Expand All @@ -236,7 +239,7 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
):
return (
InjectFlightResponse(
result=InjectFlightResult.Rejected,
result=InjectFlightResponseResult.Rejected,
notes=f"Operational intent specifies an off-nominal volume while being in {req_body.operational_intent.state} state",
),
200,
Expand Down Expand Up @@ -278,10 +281,10 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
else None
)
state_transition_to = OperationalIntentState(req_body.operational_intent.state)
if not op_intent_transition_valid(state_transition_from, state_transition_to):
if not scd.op_intent_transition_valid(state_transition_from, state_transition_to):
return (
InjectFlightResponse(
result=InjectFlightResult.Rejected,
result=InjectFlightResponseResult.Rejected,
notes=f"Operational intent state transition from {state_transition_from} to {state_transition_to} is invalid",
),
200,
Expand All @@ -294,7 +297,7 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
logger.debug(
f"[inject_flight/{pid}:{flight_id}] Obtaining latest operational intent information"
)
vol4 = Volume4DCollection.from_f3548v21(
vol4 = Volume4DCollection.from_interuss_scd_api(
req_body.operational_intent.volumes
).bounding_volume.to_f3548v21()
op_intents = query_operational_intents(vol4)
Expand All @@ -304,7 +307,9 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
logger.debug(
f"[inject_flight/{pid}:{flight_id}] Checking for intersections with {', '.join(op_intent.reference.id for op_intent in op_intents)}"
)
v1 = Volume4DCollection.from_f3548v21(req_body.operational_intent.volumes)
v1 = Volume4DCollection.from_interuss_scd_api(
req_body.operational_intent.volumes
)
for op_intent in op_intents:
if (
existing_flight
Expand All @@ -330,7 +335,7 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
)
continue

v2 = Volume4DCollection.from_f3548v21(
v2 = Volume4DCollection.from_interuss_scd_api(
op_intent.details.volumes + op_intent.details.off_nominal_volumes
)

Expand All @@ -353,7 +358,8 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
notes = f"Requested flight (priority {req_body.operational_intent.priority}) intersected {op_intent.reference.manager}'s operational intent {op_intent.reference.id} (priority {op_intent.details.priority})"
return (
InjectFlightResponse(
result=InjectFlightResult.ConflictWithFlight, notes=notes
result=InjectFlightResponseResult.ConflictWithFlight,
notes=notes,
),
200,
)
Expand Down Expand Up @@ -430,9 +436,9 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
result.operational_intent_reference.state
== OperationalIntentState.Activated
):
injection_result = InjectFlightResult.ReadyToFly
injection_result = InjectFlightResponseResult.ReadyToFly
else:
injection_result = InjectFlightResult.Planned
injection_result = InjectFlightResponseResult.Planned
return (
InjectFlightResponse(result=injection_result, operational_intent_id=id),
200,
Expand All @@ -442,17 +448,21 @@ def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict,
f"{e.__class__.__name__} while {step_name} for flight {flight_id}: {str(e)}"
)
return (
InjectFlightResponse(result=InjectFlightResult.Failed, notes=notes),
InjectFlightResponse(result=InjectFlightResponseResult.Failed, notes=notes),
200,
)
except requests.exceptions.ConnectionError as e:
notes = f"Connection error to {e.request.method} {e.request.url} while {step_name} for flight {flight_id}: {str(e)}"
response = InjectFlightResponse(result=InjectFlightResult.Failed, notes=notes)
response = InjectFlightResponse(
result=InjectFlightResponseResult.Failed, notes=notes
)
response["stacktrace"] = _make_stacktrace(e)
return response, 200
except QueryError as e:
notes = f"Unexpected response from remote server while {step_name} for flight {flight_id}: {str(e)}"
response = InjectFlightResponse(result=InjectFlightResult.Failed, notes=notes)
response = InjectFlightResponse(
result=InjectFlightResponseResult.Failed, notes=notes
)
response["queries"] = e.queries
response["stacktrace"] = e.stacktrace
return response, 200
Expand Down Expand Up @@ -508,7 +518,7 @@ def delete_flight(flight_id) -> Tuple[dict, int]:
if flight is None:
return (
DeleteFlightResponse(
result=DeleteFlightResult.Failed,
result=DeleteFlightResponseResult.Failed,
notes="Flight {} does not exist".format(flight_id),
),
200,
Expand Down Expand Up @@ -547,25 +557,29 @@ def delete_flight(flight_id) -> Tuple[dict, int]:
)
logger.debug(f"[delete_flight/{pid}:{flight_id}] {notes}")
return (
DeleteFlightResponse(result=DeleteFlightResult.Failed, notes=notes),
DeleteFlightResponse(result=DeleteFlightResponseResult.Failed, notes=notes),
200,
)
except requests.exceptions.ConnectionError as e:
notes = f"Connection error to {e.request.method} {e.request.url} while {step_name} for flight {flight_id}: {str(e)}"
logger.debug(f"[delete_flight/{pid}:{flight_id}] {notes}")
response = DeleteFlightResponse(result=DeleteFlightResult.Failed, notes=notes)
response = DeleteFlightResponse(
result=DeleteFlightResponseResult.Failed, notes=notes
)
response["stacktrace"] = _make_stacktrace(e)
return response, 200
except QueryError as e:
notes = f"Unexpected response from remote server while {step_name} for flight {flight_id}: {str(e)}"
logger.debug(f"[delete_flight/{pid}:{flight_id}] {notes}")
response = DeleteFlightResponse(result=DeleteFlightResult.Failed, notes=notes)
response = DeleteFlightResponse(
result=DeleteFlightResponseResult.Failed, notes=notes
)
response["queries"] = e.queries
response["stacktrace"] = e.stacktrace
return response, 200

logger.debug(f"[delete_flight/{pid}:{flight_id}] Complete.")
return DeleteFlightResponse(result=DeleteFlightResult.Closed), 200
return DeleteFlightResponse(result=DeleteFlightResponseResult.Closed), 200


@webapp.route("/scdsc/v1/clear_area_requests", methods=["POST"])
Expand Down Expand Up @@ -607,7 +621,7 @@ def make_result(success: bool, msg: str) -> ClearAreaResponse:
# Find operational intents in the DSS
step_name = "constructing DSS operational intent query"
# TODO: Simply use the req.extent 4D volume more directly
extent = Volume4D.from_f3548v21(req.extent)
extent = Volume4D.from_interuss_scd_api(req.extent)
start_time = extent.time_start.datetime
end_time = extent.time_end.datetime
area = extent.rect_bounds
Expand Down
2 changes: 1 addition & 1 deletion monitoring/mock_uss/tracer/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
import monitoring.monitorlib.mutate.scd
from monitoring.mock_uss import config, webapp
from monitoring.monitorlib.geo import make_latlng_rect, get_latlngrect_vertices
from monitoring.monitorlib.geotemporal import Volume4D
from monitoring.monitorlib.infrastructure import UTMClientSession
from monitoring.monitorlib.rid import RIDVersion
from monitoring.monitorlib.scd import Volume4D

yaml.add_representer(StringBasedDateTime, Representer.represent_str)

Expand Down
17 changes: 17 additions & 0 deletions monitoring/monitorlib/geotemporal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import s2sphere as s2sphere
from uas_standards.astm.f3411.v22a.api import Polygon
from uas_standards.astm.f3548.v21 import api as f3548v21
from uas_standards.interuss.automated_testing.scd.v1 import api as interuss_scd_api

from monitoring.monitorlib import geo
from monitoring.monitorlib.geo import LatLngPoint, Circle, Altitude, Volume3D
Expand Down Expand Up @@ -327,6 +328,11 @@ def from_f3548v21(vol: Union[f3548v21.Volume4D, dict]) -> Volume4D:
kwargs["time_end"] = Time(vol.time_end.value)
return Volume4D(**kwargs)

@staticmethod
def from_interuss_scd_api(vol: interuss_scd_api.Volume4D) -> Volume4D:
# InterUSS SCD API is field-compatible with ASTM F3548-21
return Volume4D.from_f3548v21(vol)

def to_f3548v21(self) -> f3548v21.Volume4D:
kwargs = {"volume": self.volume.to_f3548v21()}
if "time_start" in self and self.time_start:
Expand All @@ -335,6 +341,10 @@ def to_f3548v21(self) -> f3548v21.Volume4D:
kwargs["time_end"] = self.time_end.to_f3548v21()
return f3548v21.Volume4D(**kwargs)

def to_interuss_scd_api(self) -> interuss_scd_api.Volume4D:
# InterUSS SCD API is field-compatible with ASTM F3548-21
return ImplicitDict.parse(self.to_f3548v21(), interuss_scd_api.Volume4D)


def resolve_volume4d(template: Volume4DTemplate, start_of_test: datetime) -> Volume4D:
"""Resolve Volume4DTemplate into concrete Volume4D."""
Expand Down Expand Up @@ -528,5 +538,12 @@ def from_f3548v21(
volumes = [Volume4D.from_f3548v21(v) for v in vol4s]
return Volume4DCollection(volumes=volumes)

@staticmethod
def from_interuss_scd_api(
vol4s: List[interuss_scd_api.Volume4D],
) -> Volume4DCollection:
volumes = [Volume4D.from_interuss_scd_api(v) for v in vol4s]
return Volume4DCollection(volumes=volumes)

def to_f3548v21(self) -> List[f3548v21.Volume4D]:
return [v.to_f3548v21() for v in self.volumes]
30 changes: 7 additions & 23 deletions monitoring/monitorlib/scd.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import math
from datetime import datetime, timedelta
from enum import Enum
from typing import Dict, List, Optional, Tuple, Literal
from implicitdict import ImplicitDict, StringBasedDateTime
from typing import Optional

import arrow
import s2sphere
import shapely.geometry

from monitoring.monitorlib.geo import LatLngPoint, Radius
from uas_standards.astm.f3548.v21.api import (
OperationalIntentState,
Polygon,
Volume4D,
Volume3D,
Time,
Altitude,
Circle,
)

from monitoring.monitorlib import geo
from uas_standards.astm.f3548.v21.constants import Scope


DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
Expand All @@ -28,11 +12,11 @@
API_1_0_0 = "1.0.0"
API_0_3_17 = API_1_0_0

SCOPE_SC = "utm.strategic_coordination"
SCOPE_CM = "utm.constraint_management"
SCOPE_CP = "utm.constraint_processing"
SCOPE_CM_SA = "utm.conformance_monitoring_sa"
SCOPE_AA = "utm.availability_arbitration"
SCOPE_SC = Scope.StrategicCoordination
SCOPE_CM = Scope.ConstraintManagement
SCOPE_CP = Scope.ConstraintProcessing
SCOPE_CM_SA = Scope.ConformanceMonitoringForSituationalAwareness
SCOPE_AA = Scope.AvailabilityArbitration

NO_OVN_PHRASES = {"", "Available from USS"}

Expand Down
Loading

0 comments on commit 0d773a2

Please sign in to comment.