Skip to content

Commit 0d76b81

Browse files
[mock_uss] Make op_intent optional in mock_uss FlightRecord (#1355)
Co-authored-by: Michael Barroco <michael@orbitalize.com>
1 parent bb3d4c4 commit 0d76b81

File tree

5 files changed

+134
-135
lines changed

5 files changed

+134
-135
lines changed

.basedpyright/baseline.json

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,14 +1127,6 @@
11271127
"lineCount": 1
11281128
}
11291129
},
1130-
{
1131-
"code": "reportOptionalMemberAccess",
1132-
"range": {
1133-
"startColumn": 32,
1134-
"endColumn": 41,
1135-
"lineCount": 1
1136-
}
1137-
},
11381130
{
11391131
"code": "reportOptionalMemberAccess",
11401132
"range": {
@@ -1167,22 +1159,6 @@
11671159
"lineCount": 1
11681160
}
11691161
},
1170-
{
1171-
"code": "reportOperatorIssue",
1172-
"range": {
1173-
"startColumn": 12,
1174-
"endColumn": 62,
1175-
"lineCount": 2
1176-
}
1177-
},
1178-
{
1179-
"code": "reportArgumentType",
1180-
"range": {
1181-
"startColumn": 12,
1182-
"endColumn": 62,
1183-
"lineCount": 2
1184-
}
1185-
},
11861162
{
11871163
"code": "reportOperatorIssue",
11881164
"range": {
@@ -1811,22 +1787,6 @@
18111787
"lineCount": 1
18121788
}
18131789
},
1814-
{
1815-
"code": "reportOptionalMemberAccess",
1816-
"range": {
1817-
"startColumn": 49,
1818-
"endColumn": 55,
1819-
"lineCount": 1
1820-
}
1821-
},
1822-
{
1823-
"code": "reportOptionalMemberAccess",
1824-
"range": {
1825-
"startColumn": 68,
1826-
"endColumn": 71,
1827-
"lineCount": 1
1828-
}
1829-
},
18301790
{
18311791
"code": "reportOptionalMemberAccess",
18321792
"range": {

monitoring/mock_uss/f3548v21/flight_planning.py

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from monitoring.mock_uss.app import webapp
1212
from monitoring.mock_uss.config import KEY_BASE_URL
1313
from monitoring.mock_uss.f3548v21 import utm_client
14-
from monitoring.mock_uss.flights.database import FlightRecord, db
14+
from monitoring.mock_uss.flights.database import Database, FlightRecord, db
1515
from monitoring.monitorlib.clients import scd as scd_client
1616
from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo
1717
from monitoring.monitorlib.fetch import QueryError
@@ -104,9 +104,10 @@ def conflicts_with_flightrecords(
104104
)
105105

106106
for other_flight in flights:
107-
if not other_flight:
107+
if not other_flight or not other_flight.op_intent:
108108
continue
109109

110+
# TODO(mock_uss_flight_id): Use flight ID that is independent of op_intent
110111
if other_flight.op_intent.reference.id == op_intent.reference.id: # Same flight
111112
continue
112113

@@ -158,6 +159,7 @@ def log(msg):
158159
for op_intent in op_intents:
159160
if (
160161
existing_flight
162+
and existing_flight.op_intent
161163
and existing_flight.op_intent.reference.id == op_intent.reference.id
162164
):
163165
log(
@@ -189,11 +191,12 @@ def log(msg):
189191

190192
modifying_activated = (
191193
existing_flight
194+
and existing_flight.op_intent
192195
and existing_flight.op_intent.reference.state
193196
== scd_api.OperationalIntentState.Activated
194197
and op_intent.reference.state == scd_api.OperationalIntentState.Activated
195198
)
196-
if modifying_activated:
199+
if modifying_activated and existing_flight and existing_flight.op_intent:
197200
preexisting_conflict = Volume4DCollection.from_interuss_scd_api(
198201
existing_flight.op_intent.details.volumes
199202
).intersects_vol4s(v2)
@@ -216,22 +219,24 @@ def op_intent_transition_valid(
216219
transition_to: scd_api.OperationalIntentState | None,
217220
) -> bool:
218221
valid_states = {
222+
None,
219223
scd_api.OperationalIntentState.Accepted,
220224
scd_api.OperationalIntentState.Activated,
221225
scd_api.OperationalIntentState.Nonconforming,
222226
scd_api.OperationalIntentState.Contingent,
223227
}
224-
if transition_from is not None and transition_from not in valid_states:
228+
if transition_from not in valid_states:
225229
raise ValueError(
226230
f"Cannot transition from state {transition_from} as it is an invalid operational intent state"
227231
)
228-
if transition_to is not None and transition_to not in valid_states:
232+
if transition_to not in valid_states:
229233
raise ValueError(
230234
f"Cannot transition to state {transition_to} as it is an invalid operational intent state"
231235
)
232236

233237
if transition_from is None:
234238
return transition_to in {
239+
None,
235240
scd_api.OperationalIntentState.Accepted,
236241
scd_api.OperationalIntentState.Activated,
237242
}
@@ -385,6 +390,10 @@ def op_intent_from_flightinfo(
385390
def op_intent_from_flightrecord(
386391
flight: FlightRecord, method: str
387392
) -> f3548_v21.OperationalIntent:
393+
if not flight.op_intent:
394+
raise RuntimeError(
395+
"op_intent_from_flightrecord was called with a FlightRecord containing no op intent"
396+
)
388397
ref = flight.op_intent.reference
389398
details = f3548_v21.OperationalIntentDetails(
390399
volumes=flight.op_intent.details.volumes,
@@ -423,9 +432,13 @@ def query_operational_intents(
423432
op_intent_refs = scd_client.query_operational_intent_references(
424433
utm_client, area_of_interest
425434
)
426-
tx = db.value
435+
dbcontent: Database = db.value
427436
get_details_for = []
428-
own_flights = {f.op_intent.reference.id: f for f in tx.flights.values() if f}
437+
own_flights = {
438+
f.op_intent.reference.id: f
439+
for f in dbcontent.flights.values()
440+
if f and f.op_intent
441+
}
429442
result = []
430443
for op_intent_ref in op_intent_refs:
431444
if op_intent_ref.id in own_flights:
@@ -434,12 +447,12 @@ def query_operational_intents(
434447
op_intent_from_flightrecord(own_flights[op_intent_ref.id], "GET")
435448
)
436449
elif (
437-
op_intent_ref.id in tx.cached_operations
438-
and tx.cached_operations[op_intent_ref.id].reference.version
450+
op_intent_ref.id in dbcontent.cached_operations
451+
and dbcontent.cached_operations[op_intent_ref.id].reference.version
439452
== op_intent_ref.version
440453
):
441454
# We have a current version of this op intent cached
442-
result.append(tx.cached_operations[op_intent_ref.id])
455+
result.append(dbcontent.cached_operations[op_intent_ref.id])
443456
else:
444457
# We need to get the details for this op intent
445458
get_details_for.append(op_intent_ref)
@@ -537,57 +550,63 @@ def check_op_intent(
537550
# Check the transition is valid
538551
state_transition_from = (
539552
f3548_v21.OperationalIntentState(existing_flight.op_intent.reference.state)
540-
if existing_flight
553+
if existing_flight and existing_flight.op_intent
541554
else None
542555
)
543556
state_transition_to = f3548_v21.OperationalIntentState(
544-
new_flight.op_intent.reference.state
557+
new_flight.op_intent.reference.state if new_flight.op_intent else None
545558
)
546559
if not op_intent_transition_valid(state_transition_from, state_transition_to):
547560
raise PlanningError(
548561
f"Operational intent state transition from {state_transition_from} to {state_transition_to} is invalid"
549562
)
550563

551-
# Check the priority is allowed in the locality
552-
priority = priority_of(new_flight.op_intent.details)
553-
if (
554-
priority > locality.highest_priority()
555-
or priority <= locality.lowest_bound_priority()
556-
):
557-
raise PlanningError(
558-
f"Operational intent priority {priority} is outside the bounds of the locality priority range (]{locality.lowest_bound_priority()},{locality.highest_priority()}])"
559-
)
564+
if new_flight.op_intent:
565+
# Check the priority is allowed in the locality
566+
priority = priority_of(new_flight.op_intent.details)
567+
if (
568+
priority > locality.highest_priority()
569+
or priority <= locality.lowest_bound_priority()
570+
):
571+
raise PlanningError(
572+
f"Operational intent priority {priority} is outside the bounds of the locality priority range (]{locality.lowest_bound_priority()},{locality.highest_priority()}])"
573+
)
560574

561-
if new_flight.op_intent.reference.state in (
562-
f3548_v21.OperationalIntentState.Accepted,
563-
f3548_v21.OperationalIntentState.Activated,
564-
):
565-
# Check for intersections if the flight is nominal
575+
if new_flight.op_intent.reference.state in (
576+
f3548_v21.OperationalIntentState.Accepted,
577+
f3548_v21.OperationalIntentState.Activated,
578+
):
579+
# Check for intersections if the flight is nominal
566580

567-
# Check for operational intents in the DSS
568-
log("Obtaining latest operational intent information")
569-
v1 = Volume4DCollection.from_interuss_scd_api(
570-
new_flight.op_intent.details.volumes
571-
+ new_flight.op_intent.details.off_nominal_volumes
572-
)
573-
vol4 = v1.bounding_volume.to_f3548v21()
574-
op_intents = query_operational_intents(locality, vol4)
581+
# Check for operational intents in the DSS
582+
log("Obtaining latest operational intent information")
583+
v1 = Volume4DCollection.from_f3548v21(
584+
(new_flight.op_intent.details.volumes or [])
585+
+ (new_flight.op_intent.details.off_nominal_volumes or [])
586+
)
587+
vol4 = v1.bounding_volume.to_f3548v21()
588+
op_intents = query_operational_intents(locality, vol4)
575589

576-
# Check for intersections
577-
log(
578-
f"Checking for intersections with {', '.join(op_intent.reference.id for op_intent in op_intents)}"
579-
)
580-
has_conflicts = check_for_conflicts(
581-
new_flight.op_intent, existing_flight, op_intents, locality, log
582-
)
590+
# Check for intersections
591+
log(
592+
f"Checking for intersections with {', '.join(op_intent.reference.id for op_intent in op_intents)}"
593+
)
594+
has_conflicts = check_for_conflicts(
595+
new_flight.op_intent, existing_flight, op_intents, locality, log
596+
)
597+
598+
key = [
599+
f3548_v21.EntityOVN(op.reference.ovn)
600+
for op in op_intents
601+
if op.reference.ovn is not None
602+
]
603+
else:
604+
# Flight is not nominal and therefore doesn't need to check intersections
605+
key = []
606+
has_conflicts = False
583607

584-
key = [
585-
f3548_v21.EntityOVN(op.reference.ovn)
586-
for op in op_intents
587-
if op.reference.ovn is not None
588-
]
589608
else:
590-
# Flight is not nominal and therefore doesn't need to check intersections
609+
# Flight does not have an op intent
591610
key = []
592611
has_conflicts = False
593612

@@ -611,6 +630,10 @@ def share_op_intent(
611630
* ConnectionError
612631
* requests.exceptions.ConnectionError
613632
"""
633+
if not new_flight.op_intent:
634+
raise RuntimeError(
635+
"share_op_intent called with new_flight that is missing an op_intent"
636+
)
614637
# Create operational intent in DSS
615638
log("Sharing operational intent with DSS")
616639
base_url = new_flight.op_intent.reference.uss_base_url
@@ -624,7 +647,7 @@ def share_op_intent(
624647
uss_base_url=base_url
625648
),
626649
)
627-
if existing_flight:
650+
if existing_flight and existing_flight.op_intent:
628651
id = existing_flight.op_intent.reference.id
629652
log(f"Updating existing operational intent {id} in DSS")
630653
result = scd_client.update_operational_intent_reference(

monitoring/mock_uss/f3548v21/routes_scd.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ def scdsc_get_operational_intent_details(entityid: str):
3737
tx = db.value
3838
flight = None
3939
for f in tx.flights.values():
40-
if f and f.op_intent.reference.id == entityid:
40+
if f and f.op_intent and f.op_intent.reference.id == entityid:
4141
flight = f
4242
break
4343

4444
# If requested operational intent doesn't exist, return 404
45-
if flight is None:
45+
if flight is None or flight.op_intent is None:
4646
return (
4747
flask.jsonify(
4848
ErrorResponse(
@@ -70,7 +70,7 @@ def scdsc_get_operational_intent_telemetry(entityid: str):
7070
tx = db.value
7171
flight: FlightRecord | None = None
7272
for f in tx.flights.values():
73-
if f and f.op_intent.reference.id == entityid:
73+
if f and f.op_intent and f.op_intent.reference.id == entityid:
7474
flight = f
7575
break
7676

@@ -85,7 +85,7 @@ def scdsc_get_operational_intent_telemetry(entityid: str):
8585
404,
8686
)
8787

88-
elif flight.op_intent.reference.state not in {
88+
elif flight.op_intent and flight.op_intent.reference.state not in {
8989
OperationalIntentState.Contingent,
9090
OperationalIntentState.Nonconforming,
9191
}:

monitoring/mock_uss/flights/database.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,18 @@
1414
from monitoring.monitorlib.multiprocessing import SynchronizedValue
1515

1616
DEADLOCK_TIMEOUT = timedelta(seconds=5)
17+
1718
NOTIFICATIONS_LIMIT = timedelta(hours=1)
19+
"""Automatically remove notifications after this long beyond their observation time."""
20+
1821
DB_CLEANUP_INTERVAL = timedelta(hours=1)
22+
"""Clean database this often."""
23+
1924
FLIGHTS_LIMIT = timedelta(hours=1)
25+
"""Automatically remove flights we manage after this long beyond their end time."""
26+
2027
OPERATIONAL_INTENTS_LIMIT = timedelta(hours=1)
28+
"""Automatically remove cached operational intents obtained from others after this long beyond their end time."""
2129

2230

2331
class MockUSSFlightID(str):
@@ -27,10 +35,11 @@ class MockUSSFlightID(str):
2735

2836

2937
class FlightRecord(ImplicitDict):
30-
"""Representation of a flight in a USS"""
38+
"""Representation of a flight in mock_uss"""
3139

40+
# TODO(mock_uss_flight_id): Add flight ID that is independent of op_intent
3241
flight_info: FlightInfo
33-
op_intent: OperationalIntent
42+
op_intent: Optional[OperationalIntent] = None
3443
mod_op_sharing_behavior: Optional[MockUssFlightBehavior] = None
3544
locked: bool = False
3645

@@ -64,6 +73,7 @@ def cleanup_flights(self):
6473
if (
6574
flight
6675
and not flight.locked
76+
and flight.op_intent
6777
and flight.op_intent.reference.time_end.value.datetime + FLIGHTS_LIMIT
6878
< arrow.utcnow().datetime
6979
):
@@ -77,7 +87,7 @@ def cleanup_operational_intents(self):
7787

7888
for op_id, op_intent in self.cached_operations.items():
7989
if (
80-
op_intent.reference.time_end.value.datetime + FLIGHTS_LIMIT
90+
op_intent.reference.time_end.value.datetime + OPERATIONAL_INTENTS_LIMIT
8191
< arrow.utcnow().datetime
8292
):
8393
to_cleanup.append(op_id)

0 commit comments

Comments
 (0)