Skip to content

Commit 5ccd49c

Browse files
committed
[uss_qualifier] rid net0470 - nominalbehavior: check SP issues notifications to subscribers
1 parent 472d64d commit 5ccd49c

File tree

13 files changed

+330
-31
lines changed

13 files changed

+330
-31
lines changed

monitoring/prober/infrastructure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def wrapper_default_scope(*args, **kwargs):
100100
resource_type_code_descriptions: Dict[ResourceType, str] = {}
101101

102102

103-
# Next code: 399
103+
# Next code: 400
104104
def register_resource_type(code: int, description: str) -> ResourceType:
105105
"""Register that the specified code refers to the described resource.
106106

monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,22 @@ mock_uss_instance_uss1:
226226
participant_id: mock_uss
227227
mock_uss_base_url: http://scdsc.uss1.localutm
228228

229+
mock_uss_instance_dp_v19:
230+
resource_type: resources.interuss.mock_uss.client.MockUSSResource
231+
dependencies:
232+
auth_adapter: utm_auth
233+
specification:
234+
participant_id: mock_uss
235+
mock_uss_base_url: http://v19.riddp.uss3.localutm
236+
237+
mock_uss_instance_dp_v22a:
238+
resource_type: resources.interuss.mock_uss.client.MockUSSResource
239+
dependencies:
240+
auth_adapter: utm_auth
241+
specification:
242+
participant_id: mock_uss
243+
mock_uss_base_url: http://v22a.riddp.uss1.localutm
244+
229245
mock_uss_instance_uss6:
230246
resource_type: resources.interuss.mock_uss.client.MockUSSResource
231247
dependencies:

monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ v1:
1515
netrid_observers_v19: {$ref: 'library/environment.yaml#/netrid_observers_v19'}
1616
netrid_dss_instances_v19: {$ref: 'library/environment.yaml#/netrid_dss_instances_v19'}
1717

18+
mock_uss_instance_dp_v19: {$ref: 'library/environment.yaml#/mock_uss_instance_dp_v19'}
19+
1820
test_exclusions: { $ref: 'library/resources.yaml#/test_exclusions' }
1921
non_baseline_inputs:
2022
- v1.test_run.resources.resource_declarations.utm_auth
@@ -28,6 +30,7 @@ v1:
2830
flights_data: kentland_flights_data
2931
service_providers: netrid_service_providers_v19
3032
observers: netrid_observers_v19
33+
mock_uss: mock_uss_instance_dp_v19
3134
evaluation_configuration: netrid_observation_evaluation_configuration
3235
dss_instances: netrid_dss_instances_v19
3336
utm_client_identity: utm_client_identity

monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ v1:
1515
netrid_observers_v22a: {$ref: 'library/environment.yaml#/netrid_observers_v22a'}
1616
netrid_dss_instances_v22a: {$ref: 'library/environment.yaml#/netrid_dss_instances_v22a'}
1717

18+
mock_uss_instance_dp_v22a: {$ref: 'library/environment.yaml#/mock_uss_instance_dp_v22a'}
19+
1820
test_exclusions: { $ref: 'library/resources.yaml#/test_exclusions' }
1921
non_baseline_inputs:
2022
- v1.test_run.resources.resource_declarations.utm_auth
@@ -28,6 +30,7 @@ v1:
2830
flights_data: kentland_flights_data
2931
service_providers: netrid_service_providers_v22a
3032
observers: netrid_observers_v22a
33+
mock_uss: mock_uss_instance_dp_v22a
3134
evaluation_configuration: netrid_observation_evaluation_configuration
3235
dss_instances: netrid_dss_instances_v22a
3336
utm_client_identity: utm_client_identity

monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
from typing import List, Optional
1+
from datetime import timedelta
2+
from typing import List, Optional, Type
23

4+
from future.backports.datetime import datetime
5+
from implicitdict import ImplicitDict
6+
from loguru import logger
37
from requests.exceptions import RequestException
48
from s2sphere import LatLngRect
9+
from uas_standards.astm.f3411.v19 import api as api_v19
10+
from uas_standards.astm.f3411.v22a import api as api_v22a
511

612
from monitoring.monitorlib.errors import stacktrace_string
713
from monitoring.monitorlib.rid import RIDVersion
14+
from monitoring.monitorlib.temporal import Time
15+
from monitoring.prober.infrastructure import register_resource_type
816
from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstancesResource
17+
from monitoring.uss_qualifier.resources.interuss import IDGeneratorResource
18+
from monitoring.uss_qualifier.resources.interuss.mock_uss.client import (
19+
MockUSSResource,
20+
MockUSSClient,
21+
)
922
from monitoring.uss_qualifier.resources.netrid import (
1023
FlightDataResource,
1124
NetRIDServiceProviders,
@@ -16,6 +29,7 @@
1629
display_data_evaluator,
1730
injection,
1831
)
32+
from monitoring.uss_qualifier.scenarios.astm.netrid.dss_wrapper import DSSWrapper
1933
from monitoring.uss_qualifier.scenarios.astm.netrid.injected_flight_collection import (
2034
InjectedFlightCollection,
2135
)
@@ -31,30 +45,42 @@
3145

3246

3347
class NominalBehavior(GenericTestScenario):
48+
SUB_TYPE = register_resource_type(399, "Subscription")
49+
3450
_flights_data: FlightDataResource
3551
_service_providers: NetRIDServiceProviders
3652
_observers: NetRIDObserversResource
53+
_mock_uss: MockUSSClient
3754
_evaluation_configuration: EvaluationConfigurationResource
3855

3956
_injected_flights: List[InjectedFlight]
4057
_injected_tests: List[InjectedTest]
4158

59+
_dss_wrapper: DSSWrapper
60+
_subscription_id: str
61+
4262
def __init__(
4363
self,
4464
flights_data: FlightDataResource,
4565
service_providers: NetRIDServiceProviders,
4666
observers: NetRIDObserversResource,
67+
mock_uss: Optional[MockUSSResource],
4768
evaluation_configuration: EvaluationConfigurationResource,
48-
dss_pool: Optional[DSSInstancesResource] = None,
69+
id_generator: IDGeneratorResource,
70+
dss_pool: DSSInstancesResource,
4971
):
5072
super().__init__()
5173
self._flights_data = flights_data
5274
self._service_providers = service_providers
5375
self._observers = observers
76+
self._mock_uss = mock_uss.mock_uss
5477
self._evaluation_configuration = evaluation_configuration
5578
self._dss_pool = dss_pool
79+
self._dss_wrapper = DSSWrapper(self, dss_pool.dss_instances[0])
5680
self._injected_tests = []
5781

82+
self._subscription_id = id_generator.id_factory.make_id(self.SUB_TYPE)
83+
5884
@property
5985
def _rid_version(self) -> RIDVersion:
6086
raise NotImplementedError(
@@ -63,17 +89,74 @@ def _rid_version(self) -> RIDVersion:
6389

6490
def run(self, context: ExecutionContext):
6591
self.begin_test_scenario(context)
92+
93+
self.begin_test_case("Setup")
94+
95+
if not self._mock_uss:
96+
self.record_note(
97+
"notification_testing",
98+
"Mock USS not available, will skip checks related to notifications",
99+
)
100+
101+
self.begin_test_step("Clean workspace")
102+
# Test flights are being taken care of by preparation step before this scenario
103+
self._dss_wrapper.cleanup_sub(self._subscription_id)
104+
105+
self.end_test_step()
106+
self.end_test_case()
107+
66108
self.begin_test_case("Nominal flight")
67109

110+
if self._mock_uss:
111+
self.begin_test_step("Mock USS Subscription")
112+
self._subscribe_mock_uss()
113+
self.end_test_step()
114+
68115
self.begin_test_step("Injection")
69116
self._inject_flights()
70117
self.end_test_step()
71118

72119
self._poll_during_flights()
73120

121+
if self._mock_uss:
122+
self.begin_test_step("Validate Mock USS received notification")
123+
self._validate_mock_uss_notifications(context.start_time)
124+
self.end_test_step()
125+
74126
self.end_test_case()
75127
self.end_test_scenario()
76128

129+
def _subscribe_mock_uss(self):
130+
dss_wrapper = DSSWrapper(self, self._dss_pool.dss_instances[0])
131+
# Get all bounding rects for flights
132+
flight_rects = [f.get_rect() for f in self._flights_data.get_test_flights()]
133+
flight_union: Optional[LatLngRect] = None
134+
for fr in flight_rects:
135+
if flight_union is None:
136+
flight_union = fr
137+
else:
138+
flight_union = flight_union.union(fr)
139+
with self.check(
140+
"Subscription creation succeeds", dss_wrapper.participant_id
141+
) as check:
142+
cs = dss_wrapper.put_sub(
143+
check,
144+
[flight_union.get_vertex(k) for k in range(4)],
145+
0,
146+
3000,
147+
datetime.now(),
148+
datetime.now() + timedelta(hours=1),
149+
self._mock_uss.base_url + "/mock/riddp",
150+
self._subscription_id,
151+
None,
152+
)
153+
if not cs.success:
154+
check.record_failed(
155+
summary="Error while creating a Subscription for the Mock USS on the DSS",
156+
details=f"Error message: {cs.errors}",
157+
)
158+
return
159+
77160
def _inject_flights(self):
78161
(self._injected_flights, self._injected_tests) = injection.inject_flights(
79162
self, self._flights_data, self._service_providers
@@ -110,8 +193,98 @@ def poll_fct(rect: LatLngRect) -> bool:
110193
poll_fct,
111194
)
112195

196+
def _validate_mock_uss_notifications(self, scenario_start_time: datetime):
197+
interactions, q = self._mock_uss.get_interactions(Time(scenario_start_time))
198+
if q.status_code != 200:
199+
logger.error(
200+
f"Failed to get interactions from mock uss: HTTP {q.status_code} - {q.response.json}"
201+
)
202+
self.record_note(
203+
"mock_uss_interactions",
204+
f"failed to obtain interactions with http status {q.status_code}",
205+
)
206+
return
207+
208+
logger.debug(
209+
f"Received {len(interactions)} interactions from mock uss:\n{interactions}"
210+
)
211+
212+
# For each of the service providers we injected flights in,
213+
# we're looking for an inbound notification for the mock_uss's subscription:
214+
for test_flight in self._injected_flights:
215+
notification_reception_times = []
216+
with self.check(
217+
"Service Provider issued a notification", test_flight.uss_participant_id
218+
) as check:
219+
notif_param_type = self._notif_param_type()
220+
sub_notif_interactions: List[(datetime, notif_param_type)] = [
221+
(
222+
i.query.request.received_at.datetime,
223+
ImplicitDict.parse(i.query.request.json, notif_param_type),
224+
)
225+
for i in interactions
226+
if i.query.request.method == "POST"
227+
and i.direction == "Incoming"
228+
and "/uss/identification_service_areas/" in i.query.request.url
229+
]
230+
for (received_at, notification) in sub_notif_interactions:
231+
for sub in notification.subscriptions:
232+
if (
233+
sub.subscription_id == self._subscription_id
234+
and notification.service_area.owner
235+
== test_flight.uss_participant_id
236+
):
237+
notification_reception_times.append(received_at)
238+
239+
if len(notification_reception_times) == 0:
240+
check.record_failed(
241+
summary="No notification received",
242+
details=f"No notification received from {test_flight.uss_participant_id} for subscription {self._subscription_id} about flight {test_flight.test_id} that happened within the subscription's boundaries.",
243+
)
244+
continue
245+
246+
# The performance requirements define 95th and 99th percentiles for the SP to respect,
247+
# which we can't strictly check with one (or very few) samples.
248+
# Furthermore, we use the time of injection as the 'starting point', which is necessarily before the SP
249+
# actually becomes aware of the subscription (when the ISA is created at the DSS)
250+
# the p95 to respect is 1 second, the p99 is 3 seconds.
251+
# As an approximation, we check that the single sample (or the average of the few) is below the p99.
252+
notif_latencies = [
253+
l - test_flight.query_timestamp for l in notification_reception_times
254+
]
255+
avg_latency = (
256+
sum(notif_latencies, timedelta(0)) / len(notif_latencies)
257+
if notif_latencies
258+
else None
259+
)
260+
with self.check(
261+
"Service Provider notification was received within delay",
262+
test_flight.uss_participant_id,
263+
) as check:
264+
if avg_latency.seconds > self._rid_version.dp_data_resp_percentile99_s:
265+
check.record_failed(
266+
summary="Notification received too late",
267+
details=f"Notification(s) received {avg_latency} after the flight ended, which is more than the allowed 99th percentile of {self._rid_version.dp_data_resp_percentile99_s} seconds.",
268+
)
269+
270+
def _notif_param_type(
271+
self,
272+
) -> Type[
273+
api_v19.PutIdentificationServiceAreaNotificationParameters
274+
| api_v22a.PutIdentificationServiceAreaNotificationParameters
275+
]:
276+
if self._rid_version == RIDVersion.f3411_19:
277+
return api_v19.PutIdentificationServiceAreaNotificationParameters
278+
elif self._rid_version == RIDVersion.f3411_22a:
279+
return api_v22a.PutIdentificationServiceAreaNotificationParameters
280+
else:
281+
raise ValueError(f"Unsupported RID version: {self._rid_version}")
282+
113283
def cleanup(self):
114284
self.begin_cleanup()
285+
286+
self._dss_wrapper.cleanup_sub(self._subscription_id)
287+
115288
while self._injected_tests:
116289
injected_test = self._injected_tests.pop()
117290
matching_sps = [

monitoring/uss_qualifier/scenarios/astm/netrid/v19/nominal_behavior.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ A set of [`NetRIDServiceProviders`](../../../../resources/netrid/service_provide
1818

1919
A set of [`NetRIDObserversResource`](../../../../resources/netrid/observers.py) to be tested via checking their observations of the NetRID system and comparing the observations against expectations. An observer generally represents a "Display Application", in ASTM F3411 terminology. This scenario requires at least one observer.
2020

21+
### mock_uss
22+
23+
(Optional) MockUSSResource for testing notification delivery. If left unspecified, the scenario will not run any notification-related checks.
24+
2125
### evaluation_configuration
2226

2327
This [`EvaluationConfigurationResource`](../../../../resources/netrid/evaluation.py) defines how to gauge success when observing the injected flights.
@@ -26,8 +30,22 @@ This [`EvaluationConfigurationResource`](../../../../resources/netrid/evaluation
2630

2731
If specified, uss_qualifier will act as a Display Provider and check a DSS instance from this [`DSSInstanceResource`](../../../../resources/astm/f3411/dss.py) for appropriate identification service areas and then query the corresponding USSs with flights using the same session.
2832

33+
## Setup test case
34+
35+
### [Clean workspace test step](./dss/test_steps/clean_workspace.md)
36+
2937
## Nominal flight test case
3038

39+
### Mock USS Subscription test step
40+
41+
Before injecting the test flights, a subscription is created on the DSS for the configured mock USS to allow it
42+
to validate that Servie Providers under test correctly send out notifications.
43+
44+
#### ⚠️ Subscription creation succeeds check
45+
46+
As per **[astm.f3411.v19.DSS0030,c](../../../../requirements/astm/f3411/v19.md)**, the DSS API must allow callers to create a subscription with either onr or both of the
47+
start and end time missing, provided all the required parameters are valid.
48+
3149
### Injection test step
3250

3351
In this step, uss_qualifier injects a single nominal flight into each SP under test, usually with a start time in the future. Each SP is expected to queue the provided telemetry and later simulate that telemetry coming from an aircraft at the designated timestamps.
@@ -120,10 +138,33 @@ Per **[interuss.automated_testing.rid.observation.UniqueFlights](../../../../req
120138

121139
Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../../requirements/interuss/automated_testing/rid/observation.md)**, the call for flight details is expected to succeed since a valid ID was provided by uss_qualifier.
122140

141+
### Validate Mock USS received notification test step
142+
143+
This test step verifies that the mock_uss for which a subscription was registered before flight injection properly received a notification from each Service Provider
144+
at which a flight was injected.
145+
146+
#### ⚠️ Service Provider issued a notification check
147+
148+
This check validates that each Service Provider at which a test flight was injected properly notified the mock_uss.
149+
150+
If this is not the case, the respective Service Provider fails to meet **[astm.f3411.v19.NET0740](../../../../requirements/astm/f3411/v19.md)**.
151+
152+
#### ⚠️ Service Provider notification was received within delay check
153+
154+
This check validates that the notification from each Service Provider was received by the mock_uss within the specified delay.
155+
156+
**[astm.f3411.v19.NET0740](../../../../requirements/astm/f3411/v19.md)** states that a Service Provider must notify the owner of a subscription within `NetDpDataResponse95thPercentile` (1 second) second 95% of the time and `NetDpDataResponse99thPercentile` (3 seconds) 99% of the time as soon as the SP becomes aware of the subscription.
157+
158+
This check will be failed if it takes longer than 3 seconds between the injection of the flight and the notification being received by the mock_uss.
159+
123160
## Cleanup
124161

125162
The cleanup phase of this test scenario attempts to remove injected data from all SPs.
126163

127164
### ⚠️ Successful test deletion check
128165

129166
**[interuss.automated_testing.rid.injection.DeleteTestSuccess](../../../../requirements/interuss/automated_testing/rid/injection.md)**
167+
168+
### [Clean Subscriptions](./dss/test_steps/clean_workspace.md)
169+
170+
Remove all created subscriptions from the DSS.

0 commit comments

Comments
 (0)