Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[uss_qualifier/netrid/dss/isa_simple] Refactor and complete create case #204

Merged
Merged
4 changes: 3 additions & 1 deletion monitoring/mock_uss/riddp/routes_observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ def riddp_display_data() -> Tuple[str, int]:

# Get ISAs in the DSS
t = arrow.utcnow().datetime
isa_list: FetchedISAs = fetch.isas(view, t, t, rid_version, utm_client)
isa_list: FetchedISAs = fetch.isas(
geo.get_latlngrect_vertices(view), t, t, rid_version, utm_client
)
if not isa_list.success:
msg = f"Error fetching ISAs from DSS: {isa_list.errors}"
logger.error(msg)
Expand Down
4 changes: 2 additions & 2 deletions monitoring/mock_uss/tracer/tracer_poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from monitoring.mock_uss.tracer import context
from monitoring.monitorlib.fetch.rid import FetchedISAs
from monitoring.monitorlib.fetch.scd import FetchedEntities
from monitoring.monitorlib.geo import make_latlng_rect
from monitoring.monitorlib.geo import make_latlng_rect, get_latlngrect_vertices
from monitoring.monitorlib.infrastructure import UTMClientSession
from monitoring.monitorlib.multiprocessing import SynchronizedValue

Expand Down Expand Up @@ -97,7 +97,7 @@ def poll_observation_areas() -> None:

def poll_isas(area: ObservationArea, logger: tracerlog.Logger) -> None:
rid_client = context.get_client(area.f3411.auth_spec, area.f3411.dss_base_url)
box = make_latlng_rect(area.area.volume)
box = get_latlngrect_vertices(make_latlng_rect(area.area.volume))

log_name = "poll_isas"
t0 = datetime.datetime.utcnow()
Expand Down
10 changes: 6 additions & 4 deletions monitoring/monitorlib/fetch/rid.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ def has_different_content_than(self, other: Any) -> bool:


def isas(
box: s2sphere.LatLngRect,
area: List[s2sphere.LatLng],
start_time: datetime.datetime,
end_time: datetime.datetime,
rid_version: RIDVersion,
Expand All @@ -665,7 +665,7 @@ def isas(
t1 = rid_version.format_time(end_time)
if rid_version == RIDVersion.f3411_19:
op = v19.api.OPERATIONS[v19.api.OperationID.SearchIdentificationServiceAreas]
area = rid_v1.geo_polygon_string_from_s2(geo.get_latlngrect_vertices(box))
area = rid_v1.geo_polygon_string_from_s2(area)
url = f"{dss_base_url}{op.path}?area={area}&earliest_time={t0}&latest_time={t1}"
return FetchedISAs(
v19_query=fetch.query_and_describe(
Expand All @@ -674,7 +674,7 @@ def isas(
)
elif rid_version == RIDVersion.f3411_22a:
op = v22a.api.OPERATIONS[v22a.api.OperationID.SearchIdentificationServiceAreas]
area = rid_v2.geo_polygon_string_from_s2(geo.get_latlngrect_vertices(box))
area = rid_v2.geo_polygon_string_from_s2(area)
url = f"{dss_base_url}{op.path}?area={area}&earliest_time={t0}&latest_time={t1}"
return FetchedISAs(
v22a_query=fetch.query_and_describe(
Expand Down Expand Up @@ -948,7 +948,9 @@ def all_flights(
enhanced_details: bool = False,
) -> FetchedFlights:
t = datetime.datetime.utcnow()
isa_list = isas(area, t, t, rid_version, session, dss_base_url)
isa_list = isas(
geo.get_latlngrect_vertices(area), t, t, rid_version, session, dss_base_url
)

uss_flight_queries: Dict[str, FetchedUSSFlights] = {}
uss_flight_details_queries: Dict[str, FetchedUSSFlightDetails] = {}
Expand Down
13 changes: 13 additions & 0 deletions monitoring/monitorlib/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,16 @@ def to_vertices(self) -> List[s2sphere.LatLng]:
s2sphere.LatLng.from_degrees(self.lat_max, self.lng_max),
s2sphere.LatLng.from_degrees(self.lat_min, self.lng_max),
]


class LatLngVertex(ImplicitDict):
mickmis marked this conversation as resolved.
Show resolved Hide resolved
"""Vertex in latitude and longitude"""

lat: float
"""Latitude (degrees)"""

lng: float
"""Longitude (degrees)"""

def as_s2sphere(self) -> s2sphere.LatLng:
return s2sphere.LatLng.from_degrees(self.lat, self.lng)
33 changes: 31 additions & 2 deletions monitoring/monitorlib/mutate/rid.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
import yaml
from yaml.representer import Representer

from monitoring.monitorlib import fetch, infrastructure, rid_v1, rid_v2
from monitoring.monitorlib import (
fetch,
infrastructure,
rid_v1,
rid_v2,
schema_validation,
)


class ChangedSubscription(RIDQuery):
Expand Down Expand Up @@ -61,6 +67,8 @@ def errors(self) -> List[str]:
f"Error parsing F3411-22a USS PutSubscriptionResponse: {str(e)}"
]

# TODO: add schema validation (like ChangedISA)

return []

@property
Expand Down Expand Up @@ -308,7 +316,11 @@ def _v22a_response(

@property
def errors(self) -> List[str]:
if self.status_code != 200:
# Tolerate reasonable-but-technically-incorrect code 201
if not (
self.status_code == 200
or (self.mutation == "create" and self.status_code == 201)
):
return ["Failed to mutate ISA ({})".format(self.status_code)]
if self.query.response.json is None:
return ["ISA response did not include valid JSON"]
Expand Down Expand Up @@ -337,6 +349,23 @@ def errors(self) -> List[str]:
f"Error parsing F3411-22a USS PutIdentificationServiceAreaResponse: {str(e)}"
]

# TODO: This validates the schema only for v22a as the v19 OpenAPI definition contains a mistake in
# 'components.schemas.SubscriptionState', the non-existing property 'subscription' is marked as required.
mickmis marked this conversation as resolved.
Show resolved Hide resolved
# The augmented.yaml version of the definition should be fixed and then this can be applied to both version.
if self.rid_version == RIDVersion.f3411_22a:
validation_errors = schema_validation.validate(
self.rid_version.openapi_path,
self.rid_version.openapi_delete_isa_response_path
if self.mutation == "delete"
else self.rid_version.openapi_put_isa_response_path,
self.query.response.json,
mickmis marked this conversation as resolved.
Show resolved Hide resolved
)
if validation_errors:
return [
f"PUT ISA response JSON validation error: [{e.json_path}] {e.message}"
for e in validation_errors
]

return []

@property
Expand Down
13 changes: 12 additions & 1 deletion monitoring/monitorlib/rid.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from monitoring.monitorlib import schema_validation
from uas_standards.astm.f3411 import v19, v22a
import uas_standards.astm.f3411.v19.api
import uas_standards.astm.f3411.v19.constants
import uas_standards.astm.f3411.v22a.api
import uas_standards.astm.f3411.v22a.constants
Expand Down Expand Up @@ -56,6 +57,15 @@ def openapi_put_isa_response_path(self) -> str:
else:
raise ValueError(f"Unsupported RID version '{self}'")

@property
def openapi_delete_isa_response_path(self) -> str:
if self == RIDVersion.f3411_19:
return schema_validation.F3411_19.DeleteIdentificationServiceAreaResponse
elif self == RIDVersion.f3411_22a:
return schema_validation.F3411_22a.DeleteIdentificationServiceAreaResponse
else:
raise ValueError(f"Unsupported RID version '{self}'")

@property
def realtime_period(self) -> timedelta:
if self == RIDVersion.f3411_19:
Expand Down Expand Up @@ -202,7 +212,8 @@ def dss_max_subscriptions_per_area(self) -> int:

def flights_url_of(self, base_url: str) -> str:
if self == RIDVersion.f3411_19:
return base_url
flights_path = v19.api.OPERATIONS[v19.api.OperationID.SearchFlights].path
return base_url + flights_path
elif self == RIDVersion.f3411_22a:
flights_path = v22a.api.OPERATIONS[v22a.api.OperationID.SearchFlights].path
return base_url + flights_path
Expand Down
6 changes: 6 additions & 0 deletions monitoring/monitorlib/schema_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class F3411_19(str, Enum):
PutIdentificationServiceAreaResponse = (
"components.schemas.PutIdentificationServiceAreaResponse"
)
DeleteIdentificationServiceAreaResponse = (
"components.schemas.DeleteIdentificationServiceAreaResponse"
)


class F3411_22a(str, Enum):
Expand All @@ -28,6 +31,9 @@ class F3411_22a(str, Enum):
PutIdentificationServiceAreaResponse = (
"components.schemas.PutIdentificationServiceAreaResponse"
)
DeleteIdentificationServiceAreaResponse = (
"components.schemas.DeleteIdentificationServiceAreaResponse"
)


class F3548_21(str, Enum):
Expand Down
15 changes: 11 additions & 4 deletions monitoring/uss_qualifier/configurations/dev/library/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,19 @@ net_rid:
specification:
base_url: https://uss_qualifier.test.utm/dummy_base_url
footprint:
lat_min: 37.1853
lng_min: -80.6140
lat_max: 37.2148
lng_max: -80.5440
- lat: 37.1853
lng: -80.6140
- lat: 37.2148
lng: -80.6140
- lat: 37.2148
lng: -80.5440
- lat: 37.1853
lng: -80.5440
Comment on lines +35 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular benefit to using a polygon rather than a bounding box?

(I can think of a few: I was just wondering what the benefit is regarding to the tests we do)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes my implementation life easier, and the resource more flexible (notably it would be nice to use it in the DSS interoperability scenario, where specifying the polygon would be needed)

altitude_min: 0
altitude_max: 3048
reference_time: '2023-01-10T00:00:00.123456+00:00'
time_start: '2023-01-10T00:00:01.123456+00:00'
time_end: '2023-01-10T01:00:01.123456+00:00'

net_rid_sims:
adjacent_circular_flights_data:
Expand Down
26 changes: 23 additions & 3 deletions monitoring/uss_qualifier/resources/netrid/service_area.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from implicitdict import ImplicitDict
from monitoring.monitorlib.geo import LatLngBoundingBox
import datetime
from typing import List

from implicitdict import ImplicitDict, StringBasedDateTime
from monitoring.monitorlib.geo import LatLngVertex

from monitoring.uss_qualifier.resources.resource import Resource

Expand All @@ -12,7 +15,7 @@ class ServiceAreaSpecification(ImplicitDict):

This URL will probably not identify a real resource in tests."""

footprint: LatLngBoundingBox
footprint: List[LatLngVertex]
"""2D outline of service area"""

altitude_min: float = 0
Expand All @@ -21,6 +24,23 @@ class ServiceAreaSpecification(ImplicitDict):
altitude_max: float = 3048
"""Upper altitude bound of service area, meters above WGS84 ellipsoid"""

reference_time: StringBasedDateTime
"""Reference time used to adjust start and end times at runtime"""

time_start: StringBasedDateTime
"""Start time of service area (relative to reference_time)"""

time_end: StringBasedDateTime
"""End time of service area (relative to reference_time)"""

def shifted_time_start(self, now: datetime.datetime) -> datetime.datetime:
mickmis marked this conversation as resolved.
Show resolved Hide resolved
dt = now - self.reference_time.datetime
return self.time_start.datetime + dt

def shifted_time_end(self, now: datetime.datetime) -> datetime.datetime:
dt = now - self.reference_time.datetime
return self.time_end.datetime + dt


class ServiceAreaResource(Resource[ServiceAreaSpecification]):
specification: ServiceAreaSpecification
Expand Down
Loading