From 02f1eec34df188883358a76ac0ad04c78b647f5c Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Mon, 19 May 2025 09:15:48 +0200 Subject: [PATCH 1/2] [uss_qualifier] expand OIR implicit sub handling scenario --- .../astm/utm/dss/oir_implicit_sub_handling.md | 48 ++++++-- .../astm/utm/dss/oir_implicit_sub_handling.py | 107 +++++++++++++++++- 2 files changed, 139 insertions(+), 16 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md index 4241cf76d7..82210e3b1f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md @@ -92,10 +92,7 @@ If the newly created OIR does not mention the implicit subscription from the pre the DSS is either improperly managing implicit subscriptions, or failing to report the subscriptions relevant to an OIR, and therefore in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** or **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)** respectively. -#### 🛑 No implicit subscription was attached check - -If the DSS attached an implicit subscription, by either creating or re-using an existing one, to the OIR that was created in this step, -the DSS is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**. +#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_expected_subscription.md) ### Mutate OIR with implicit subscription to not overlap anymore test step @@ -135,11 +132,7 @@ that were present when the test case started. Otherwise, the DSS may be failing to properly implement **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** or **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. -#### 🛑 No implicit subscription was attached check - -If the DSS attached an implicit subscription, by either creating or re-using an existing one, to the OIR that was created in this step, -the DSS is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**. - +#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_expected_subscription.md) ## Implicit subscriptions are properly deleted when required by OIR mutation test case This test case verifies that implicit subscriptions are properly removed if they become unnecessary following the mutation of an OIR. @@ -271,10 +264,41 @@ Replace the first OIR's explicit subscription with the implicit one created in t Confirm that the query to replace the second OIR's explicit subscription with the second OIR's implicit subscription succeeds. -#### 🛑 The first OIR is now attached to the specified implicit subscription check +#### [First OIR is now attached to the specified implicit subscription](fragments/oir/oir_has_expected_subscription.md) + +## Existing implicit subscription can be attached to OIR without subscription test case + +This test case verifies that an implicit subscription can be attached to an OIR that is not currently attached to any subscription. + +### Ensure clean workspace test step + +Reset the workspace for this test case. + +#### [Clean any existing OIRs with known test IDs](clean_workspace_op_intents.md) + +#### [Clean any existing subscriptions with known test IDs](clean_workspace_subs.md) + +### [Create OIR with no subscription test step](./fragments/oir/crud/create_query.md) + +#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_expected_subscription.md) + +### [Create second OIR with an implicit subscription test step](./fragments/oir/crud/create_query.md) + +#### [An implicit subscription was created](./fragments/sub/implicit_create.md) + +### Attach OIR without subscription to implicit subscription test step + +Attach the first OIR to the implicit subscription created with the second OIR. + +#### [Attach OIR to implicit subscription](./fragments/oir/crud/update_query.md) + +### Confirm OIR is now attached to implicit subscription test step + +Confirms that the DSS properly attached the first OIR to the implicit subscription created with the second OIR. + +#### [Get OIR query](./fragments/oir/crud/read_query.md) -If the OIR is not attached to the implicit subscription specified in a successful mutation query, -the DSS is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**. +#### [First OIR is now attached to the specified implicit subscription](fragments/oir/oir_has_expected_subscription.md) ## Cleanup diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py index 67a88e7643..413b5dff77 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py @@ -1,9 +1,8 @@ from datetime import datetime, timedelta -from typing import Dict, List, Optional, Set, Tuple +from typing import List, Optional, Set, Tuple import arrow from uas_standards.astm.f3548.v21.api import ( - EntityID, OperationalIntentReference, OperationalIntentState, SubscriberToNotify, @@ -191,6 +190,12 @@ def run(self, context: ExecutionContext): self._case_5_replace_explicit_sub_with_implicit() self.end_test_case() + self.begin_test_case( + "Existing implicit subscription can be attached to OIR without subscription" + ) + self._setup_case() + self._case_6_attach_implicit_sub_to_oir_without_subscription() + self.end_test_case() self.end_test_scenario() def _case_1_step_create_oir_1(self): @@ -435,7 +440,7 @@ def _create_oir( implicit_sub = sub.subscription elif subscription_id is None: with self.check( - "No implicit subscription was attached", self._pid + "OIR is attached to expected subscription", self._pid ) as check: # The official DSS implementation will set the subscription ID to 00000000-0000-4000-8000-000000000000 # Other implementations may use a different value, as the OpenAPI spec does not allow the value to be empty @@ -777,7 +782,7 @@ def _case_5_replace_explicit_sub_with_implicit(self): self._oir_a_ovn = oir.ovn with self.check( - "The first OIR is now attached to the specified implicit subscription", + "OIR is attached to expected subscription", self._pid, ) as check: if sub_implicit.id != oir.subscription_id: @@ -787,6 +792,100 @@ def _case_5_replace_explicit_sub_with_implicit(self): ) self.end_test_step() + def _case_6_attach_implicit_sub_to_oir_without_subscription(self): + self.begin_test_step("Create OIR with no subscription") + oir_no_sub, _, _, _ = self._create_oir( + oir_id=self._oir_a_id, + time_start=self._time_2, + time_end=self._time_3, + relevant_ovns=[], + with_implicit_sub=False, + ) + self._oir_a_ovn = oir_no_sub.ovn + self.end_test_step() + + self.begin_test_step("Create second OIR with an implicit subscription") + oir_implicit, _, sub_implicit, _ = self._create_oir( + oir_id=self._oir_b_id, + time_start=oir_no_sub.time_start.value.datetime, + time_end=oir_no_sub.time_end.value.datetime, + relevant_ovns=[oir_no_sub.ovn], + with_implicit_sub=True, + ) + self._oir_b_ovn = oir_implicit.ovn + self._implicit_sub_1 = sub_implicit + self.end_test_step() + + self.begin_test_step("Attach OIR without subscription to implicit subscription") + with self.check( + "Mutate operational intent reference query succeeds", self._pid + ) as check: + try: + oir_updated, subs, q = self._dss.put_op_intent( + extents=[ + self._planning_area.get_volume4d( + oir_no_sub.time_start.value.datetime, + oir_no_sub.time_end.value.datetime, + ).to_f3548v21() + ], + key=[oir_implicit.ovn], + state=OperationalIntentState.Accepted, + base_url=DUMMY_BASE_URL, + oi_id=self._oir_a_id, + ovn=self._oir_a_ovn, + subscription_id=sub_implicit.id, + ) + self.record_query(q) + except QueryError as e: + self.record_queries(e.queries) + check.record_failed( + summary="OIR Creation failed", + details=str(e), + query_timestamps=e.query_timestamps, + ) + + self._oir_a_ovn = oir_updated.ovn + + self.end_test_step() + + self.begin_test_step("Confirm OIR is now attached to implicit subscription") + + # First, sanity check of the value reported by the DSS at the previous step + with self.check( + "OIR is attached to expected subscription", + self._pid, + ) as check: + if sub_implicit.id != oir_updated.subscription_id: + check.record_failed( + summary="Implicit subscription not attached to OIR", + details=f"The subscription {sub_implicit.id} was attached to the OIR, but it reports being attached to subscription {oir_no_sub.subscription_id} instead.", + ) + + # Then, do an actual query of the OIR + with self.check("Get operational intent reference by ID", self._pid) as check: + try: + oir_queried, _ = self._dss.get_op_intent_reference(self._oir_a_id) + + except QueryError as e: + self.record_queries(e.queries) + check.record_failed( + summary="OIR query failed", + details=str(e), + query_timestamps=e.query_timestamps, + ) + + with self.check( + "OIR is attached to expected subscription", + self._oir_a_id, + ) as check: + if sub_implicit.id != oir_queried.subscription_id: + check.record_failed( + summary="Implicit subscription not attached to OIR", + details=f"The subscription {sub_implicit.id} was attached to the OIR, but it reports being attached to subscription {oir_queried.subscription_id} instead.", + ) + + self.end_test_step() + def _setup_case(self): # T0 corresponds to 'now' self._time_0 = arrow.utcnow().datetime From bdb6d904ec2222161bfe3681705bf7dd7f69c11a Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Thu, 22 May 2025 21:09:05 +0200 Subject: [PATCH 2/2] comments --- .../astm/utm/dss/fragments/oir/__init__.py | 115 ++++++++++++++++++ .../astm/utm/dss/oir_explicit_sub_handling.py | 84 ++----------- .../astm/utm/dss/oir_implicit_sub_handling.md | 7 +- .../astm/utm/dss/oir_implicit_sub_handling.py | 11 +- 4 files changed, 137 insertions(+), 80 deletions(-) create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/__init__.py diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/__init__.py new file mode 100644 index 0000000000..c825516f5e --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/__init__.py @@ -0,0 +1,115 @@ +from typing import Optional + +from uas_standards.astm.f3548.v21.api import EntityID, SubscriptionID + +from monitoring.monitorlib.fetch import QueryError +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance +from monitoring.uss_qualifier.scenarios.scenario import TestScenarioType + +# The InterUSS DSS implementation will set an OIR's subscription ID to 00000000-0000-4000-8000-000000000000 +# when the OIR is not attached to any subscription, as the OpenAPI spec does not allow the value to be empty. +# Other implementations may use a different value. One way to check that an OIR is not attached to any subscription +# is to attempt to retrieve the subscription reportedly attached to it: if a 404 is returned then we may assume +# no subscription is attached. +# Note that this is only allowed for OIRs in the ACCEPTED state. +NULL_SUBSCRIPTION_ID = "00000000-0000-4000-8000-000000000000" + + +def step_oir_has_correct_subscription( + scenario: TestScenarioType, + dss: DSSInstance, + oir_id: EntityID, + expected_sub_id: Optional[SubscriptionID], +): + """ + Ensure that an OIR is currently attached to the specified subscription, + or not attached to any subscription if the passed subscription ID is None. + + This fragment will fetch the OIR from the DSS. + """ + + step_name = ( + "OIR is not attached to any subscription" + if expected_sub_id is None + else "OIR is attached to expected subscription" + ) + scenario.begin_test_step(step_name) + check_oir_has_correct_subscription( + scenario, + dss, + oir_id, + expected_sub_id, + ) + scenario.end_test_step() + + +def check_oir_has_correct_subscription( + scenario: TestScenarioType, + dss: DSSInstance, + oir_id: EntityID, + expected_sub_id: Optional[SubscriptionID], +): + with scenario.check( + "Get operational intent reference by ID", dss.participant_id + ) as check: + try: + oir, q = dss.get_op_intent_reference(oir_id) + scenario.record_query(q) + except QueryError as qe: + scenario.record_queries(qe.queries) + check.record_failed( + summary="Could not get OIR", + details=f"Failed to get OIR with error code {qe.cause_status_code}: {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + + sub_is_as_expected = False + referenced_sub_was_found_when_non_expected = False + if expected_sub_id is None: + # See comment on NULL_SUBSCRIPTION_ID + # ASTM may at some point decide to tolerate accepting empty returned values here, + # but in the meantime we simply attempt to obtain the subscription and check that it does not exist + if oir.subscription_id == NULL_SUBSCRIPTION_ID: + # Sub ID explicitly set to the value representing the null subscription: all good + sub_is_as_expected = True + elif oir.subscription_id is None: + # Sub ID not set at all: not compliant with the spec, but not wrong with regard to which subscription should be attached to the OIR + sub_is_as_expected = True + else: + # If the subscription ID is defined and not set to the known 'null' value, we assume that the DSS used another + # placeholder for the non-existing subscription, and we check that it does not exist. + with scenario.check( + "Subscription referenced by the OIR does not exist" + ) as check: + sub = dss.get_subscription(oir.subscription_id) + scenario.record_query(sub) + if sub.status_code not in [200, 404]: + check.record_failed( + summary="Failed to try to obtain the subscription referenced by the OIR", + details=f"Failed in an unexpected way while querying subscription with ID {oir.subscription_id}: expected a 404 or 200, but got {sub.status_code}", + query_timestamps=[sub.request.timestamp], + ) + if sub.status_code == 200: + referenced_sub_was_found_when_non_expected = True + else: + sub_is_as_expected = oir.subscription_id == expected_sub_id + + attached_check_name = ( + "OIR is not attached to a subscription" + if expected_sub_id is None + else f"OIR is attached to expected subscription" + ) + + with scenario.check(attached_check_name, dss.participant_id) as check: + if referenced_sub_was_found_when_non_expected: + check.record_failed( + summary="OIR is attached to a subscription although it should not be", + details=f"Expected OIR to not be attached to any subscription, but the referenced subscription {oir.subscription_id} does exist.", + query_timestamps=[sub.request.timestamp], + ) + if not sub_is_as_expected: + check.record_failed( + summary="OIR is not attached to the correct subscription", + details=f"Expected OIR to be attached to subscription {expected_sub_id}, but it is attached to {oir.subscription_id}", + query_timestamps=[q.request.timestamp], + ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py index d9a82f9ca0..232e6c9eb4 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py @@ -31,20 +31,15 @@ from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.oir import ( crud as oir_fragments, ) +from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.oir import ( + step_oir_has_correct_subscription, +) from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.sub import ( crud as sub_fragments, ) from monitoring.uss_qualifier.scenarios.scenario import TestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext -# The InterUSS DSS implementation will set an OIR's subscription ID to 00000000-0000-4000-8000-000000000000 -# when the OIR is not attached to any subscription, as the OpenAPI spec does not allow the value to be empty. -# Other implementations may use a different value. One way to check that an OIR is not attached to any subscription -# is to attempt to retrieve the subscription reportedly attached to it: if a 404 is returned then we may assume -# no subscription is attached. -# Note that this is only allowed for OIRs in the ACCEPTED state. -NULL_SUBSCRIPTION_ID = "00000000-0000-4000-8000-000000000000" - class OIRExplicitSubHandling(TestScenario): OIR_TYPE = register_resource_type(401, "Operational Intent Reference") @@ -377,76 +372,13 @@ def _step_update_oir_with_sufficient_explicit_sub(self, is_replacement: bool): def _step_oir_has_correct_subscription( self, expected_sub_id: Optional[SubscriptionID] ): - step_check_name = ( - "OIR is not attached to any subscription" - if expected_sub_id is None - else "OIR is attached to expected subscription" - ) - self.begin_test_step(step_check_name) - with self.check("Get operational intent reference by ID", self._pid) as check: - try: - oir, q = self._dss.get_op_intent_reference(self._oir_id) - self.record_query(q) - except QueryError as qe: - self.record_queries(qe.queries) - check.record_failed( - summary="Could not get OIR", - details=f"Failed to get OIR with error code {qe.cause_status_code}: {qe.msg}", - query_timestamps=qe.query_timestamps, - ) - - sub_is_as_expected = False - referenced_sub_was_found_when_non_expected = False - if expected_sub_id is None: - # See comment on NULL_SUBSCRIPTION_ID - # ASTM may at some point decide to tolerate accepting empty returned values here, - # but in the meantime we simply attempt to obtain the subscription and check that it does not exist - if oir.subscription_id == NULL_SUBSCRIPTION_ID: - # Sub ID explicitly set to the value representing the null subscription: all good - sub_is_as_expected = True - elif oir.subscription_id is None: - # Sub ID not set at all: not compliant with the spec, but not wrong with regard to which subscription should be attached to the OIR - sub_is_as_expected = True - else: - # If the subscription ID is defined and not set to the known 'null' value, we assume that the DSS used another - # placeholder for the non-existing subscription, and we check that it does not exist. - with self.check( - "Subscription referenced by the OIR does not exist" - ) as check: - sub = self._dss.get_subscription(oir.subscription_id) - self.record_query(sub) - if sub.status_code not in [200, 404]: - check.record_failed( - summary="Failed to try to obtain the subscription referenced by the OIR", - details=f"Failed in an unexpected way while querying subscription with ID {oir.subscription_id}: expected a 404 or 200, but got {sub.status_code}", - query_timestamps=[sub.request.timestamp], - ) - if sub.status_code == 200: - referenced_sub_was_found_when_non_expected = True - else: - sub_is_as_expected = oir.subscription_id == expected_sub_id - - attached_check_name = ( - "OIR is not attached to a subscription" - if expected_sub_id is None - else f"OIR is attached to expected subscription" + step_oir_has_correct_subscription( + self, + self._dss, + self._oir_id, + expected_sub_id, ) - with self.check(attached_check_name, self._pid) as check: - if referenced_sub_was_found_when_non_expected: - check.record_failed( - summary="OIR is attached to a subscription although it should not be", - details=f"Expected OIR to not be attached to any subscription, but the referenced subscription {oir.subscription_id} does exist.", - query_timestamps=[sub.request.timestamp], - ) - if not sub_is_as_expected: - check.record_failed( - summary="OIR is not attached to the correct subscription", - details=f"Expected OIR to be attached to subscription {expected_sub_id}, but it is attached to {oir.subscription_id}", - query_timestamps=[q.request.timestamp], - ) - self.end_test_step() - def _delete_subscription(self, sub_id: EntityID, sub_version: str): with self.check("Subscription can be deleted", self._pid) as check: sub = self._dss.delete_subscription(sub_id, sub_version) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md index 82210e3b1f..86c3dccc89 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md @@ -92,7 +92,7 @@ If the newly created OIR does not mention the implicit subscription from the pre the DSS is either improperly managing implicit subscriptions, or failing to report the subscriptions relevant to an OIR, and therefore in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** or **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)** respectively. -#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_expected_subscription.md) +#### [No implicit subscription was attached](./fragments/oir/oir_has_no_subscription.md) ### Mutate OIR with implicit subscription to not overlap anymore test step @@ -132,7 +132,8 @@ that were present when the test case started. Otherwise, the DSS may be failing to properly implement **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** or **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. -#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_expected_subscription.md) +#### [No implicit subscription was attached](./fragments/oir/oir_has_no_subscription.md) + ## Implicit subscriptions are properly deleted when required by OIR mutation test case This test case verifies that implicit subscriptions are properly removed if they become unnecessary following the mutation of an OIR. @@ -280,7 +281,7 @@ Reset the workspace for this test case. ### [Create OIR with no subscription test step](./fragments/oir/crud/create_query.md) -#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_expected_subscription.md) +#### [OIR is not attached to an implicit subscription](./fragments/oir/oir_has_no_subscription.md) ### [Create second OIR with an implicit subscription test step](./fragments/oir/crud/create_query.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py index 413b5dff77..bc8c8052a2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py @@ -20,6 +20,9 @@ from monitoring.uss_qualifier.resources.communications import ClientIdentityResource from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments +from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.oir import ( + check_oir_has_correct_subscription, +) from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.sub.crud import ( sub_create_query, ) @@ -440,7 +443,7 @@ def _create_oir( implicit_sub = sub.subscription elif subscription_id is None: with self.check( - "OIR is attached to expected subscription", self._pid + "OIR is not attached to a subscription", self._pid ) as check: # The official DSS implementation will set the subscription ID to 00000000-0000-4000-8000-000000000000 # Other implementations may use a different value, as the OpenAPI spec does not allow the value to be empty @@ -802,6 +805,12 @@ def _case_6_attach_implicit_sub_to_oir_without_subscription(self): with_implicit_sub=False, ) self._oir_a_ovn = oir_no_sub.ovn + check_oir_has_correct_subscription( + self, + self._dss, + self._oir_a_id, + expected_sub_id=None, + ) self.end_test_step() self.begin_test_step("Create second OIR with an implicit subscription")