From 3a10cf3698fec80bcf150319c099421f0519b8f5 Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Fri, 11 Oct 2024 23:28:45 +0200 Subject: [PATCH 1/3] [uss_qualifier] oir_simple: check subscription extent needs to cover OIR --- monitoring/prober/infrastructure.py | 2 +- .../oir/oir_has_expected_subscription.md | 10 + .../astm/utm/dss/op_intent_ref_simple.md | 62 +++- .../astm/utm/dss/op_intent_ref_simple.py | 276 +++++++++++++++++- 4 files changed, 334 insertions(+), 16 deletions(-) create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md diff --git a/monitoring/prober/infrastructure.py b/monitoring/prober/infrastructure.py index 4598697aa7..9022444a94 100644 --- a/monitoring/prober/infrastructure.py +++ b/monitoring/prober/infrastructure.py @@ -100,7 +100,7 @@ def wrapper_default_scope(*args, **kwargs): resource_type_code_descriptions: Dict[ResourceType, str] = {} -# Next code: 398 +# Next code: 400 def register_resource_type(code: int, description: str) -> ResourceType: """Register that the specified code refers to the described resource. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md new file mode 100644 index 0000000000..7c8bc15d78 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md @@ -0,0 +1,10 @@ +# OIR is attached to expected subscription test step fragment + +## [Query Success](./crud/read_query.md) + +Check query succeeds + +## 🛑 OIR is attached to expected subscription check + +If the OIR returned by the DSS under test is not attached to the expected subscription, +it is in violation of **[astm.f3548.v21.DSS0005,1](../../../../../../requirements/astm/f3548/v21.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md index e6c98dd7d9..1fe6fc192c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md @@ -28,14 +28,74 @@ Verifies the behavior of a DSS for simple interactions pertaining to operational This step ensures that no entities with the known test IDs exists in the DSS. +### [Create a subscription test step](./fragments/sub/crud/create_query.md) + +Create an explicit subscription to be used in this scenario. + +## Validate explicit subscription on OIR creation test case + +Ensures that the explicit subscription provided upon creation of an OIR is properly validated and attached to the OIR. + +### Provide subscription not covering extent of OIR being created test step + +This step verifies that an OIR cannot be created when an explicit subscription that does not cover the extent of the OIR is specified. + +#### 🛑 Request to create OIR with incorrect subscription fails check + +If the DSS under test allows the qualifier to create an OIR with an explicit subscription that does not cover the extent of the OIR, +it is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** + ### [Create an operational intent reference test step](./fragments/oir/crud/create_query.md) -Create an operational intent reference to be used in this scenario. +When the provided subscription covers the extent of the OIR, the OIR can be created. + +### [OIR is attached to expected subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is attached to the subscription provided upon creation. + +## Validate explicit subscription upon subscription replacement test case + +Ensures that when the explicit subscription tied to an OIR is replaced with another explicit subscription, +this subscription is properly validated and attached to the OIR. + +### [Create a subscription test step](./fragments/sub/crud/create_query.md) + +Create an additional explicit subscription to be used in this test case. + +### Attempt to replace OIR's existing explicit subscription with an insufficient one test step + +This step verifies that an OIR's existing explicit subscription cannot be replaced with an explicit subscription that does not cover the extent of the OIR. + +#### 🛑 Request to mutate OIR while providing an incorrect subscription fails check + +If the DSS under test allows the qualifier to replace an OIR's existing explicit subscription with an explicit subscription that does not cover the extent of the OIR, +it is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** + +### [OIR is attached to expected subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is still attached to the previous, valid, subscription. + +### [Replace the OIR's explicit subscription test step](./fragments/oir/crud/update_query.md) + +This step verifies that an OIR attached to an explicit subscription can be mutated in order to be attached +to another explicit subscription that properly covers the extent of the OIR. + +### [OIR is attached to expected subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is attached to the subscription provided upon mutation. ## Deletion requires correct OVN test case Ensures that a DSS will only delete OIRs when the correct OVN is presented. +### [Ensure clean workspace test step](./clean_workspace.md) + +This step resets the workspace for the present and following test cases by ensuring that no entities with the known test IDs exists in the DSS. + +### [Create an operational intent reference test step](./fragments/oir/crud/create_query.md) + +Create an operational intent reference to be used in this and the following test cases. + ### Attempt deletion with missing OVN test step This step verifies that an existing OIR cannot be deleted with a missing OVN. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py index 39b5e3dfac..30631dd50f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py @@ -5,10 +5,14 @@ EntityID, OperationalIntentReference, OperationalIntentState, + SubscriptionID, + Subscription, + PutOperationalIntentReferenceParameters, ) from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import QueryError +from monitoring.monitorlib.fetch.rid import subscription from monitoring.monitorlib.geotemporal import Volume4D from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources.astm.f3548.v21 import PlanningAreaResource @@ -19,6 +23,9 @@ from monitoring.uss_qualifier.resources.astm.f3548.v21.planning_area import ( PlanningAreaSpecification, ) +from monitoring.uss_qualifier.resources.astm.f3548.v21.subscription_params import ( + SubscriptionParams, +) 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 @@ -38,10 +45,13 @@ class OIRSimple(TestScenario): """ OIR_TYPE = register_resource_type(396, "Operational Intent Reference") - + SUB_TYPE = register_resource_type(398, "Subscription") + EXTRA_SUB_TYPE = register_resource_type(399, "Subscription") _dss: DSSInstance _oir_id: EntityID + _sub_id: SubscriptionID + _extra_sub_id: SubscriptionID # Keep track of the current OIR state _current_oir: Optional[OperationalIntentReference] @@ -49,6 +59,12 @@ class OIRSimple(TestScenario): _planning_area: PlanningAreaSpecification _planning_area_volume4d: Volume4D + # Keep track of the current subscription + _sub_params: Optional[SubscriptionParams] + _current_sub: Optional[Subscription] + + _current_extra_sub: Optional[Subscription] + def __init__( self, dss: DSSInstanceResource, @@ -73,6 +89,8 @@ def __init__( self._pid = [self._dss.participant_id] self._oir_id = id_generator.id_factory.make_id(self.OIR_TYPE) + self._sub_id = id_generator.id_factory.make_id(self.SUB_TYPE) + self._extra_sub_id = id_generator.id_factory.make_id(self.EXTRA_SUB_TYPE) self._expected_manager = client_identity.subject() @@ -84,9 +102,36 @@ def __init__( def run(self, context: ExecutionContext): self.begin_test_scenario(context) - self._setup_case() + self.begin_test_case("Setup") + self._setup_case(create_explicit_sub=True) + self.end_test_case() + + self.begin_test_case("Validate explicit subscription on OIR creation") + self._step_create_oir_insufficient_subscription() + self._step_create_oir( + oir_params=self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=datetime.now() - timedelta(seconds=10), + time_end=self._sub_params.end_time, # OIR ends at the same time as subscription + subscription_id=self._sub_id, + ), + ) + self._step_oir_has_correct_subscription(expected_sub_id=self._sub_id) + self.end_test_case() + + self.begin_test_case( + "Validate explicit subscription upon subscription replacement" + ) + self._step_update_oir_with_insufficient_explicit_sub() + self._step_oir_has_correct_subscription(expected_sub_id=self._sub_id) + self._step_update_oir_with_sufficient_explicit_sub() + self._step_oir_has_correct_subscription(expected_sub_id=self._extra_sub_id) + self.end_test_case() self.begin_test_case("Deletion requires correct OVN") + self._setup_case(create_oir=True) self._step_attempt_delete_missing_ovn() self._step_attempt_delete_incorrect_ovn() self.end_test_case() @@ -98,17 +143,35 @@ def run(self, context: ExecutionContext): self.end_test_scenario() - def _step_create_oir(self): - oir_params = self._planning_area.get_new_operational_intent_ref_params( - key=[], - state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), - time_start=datetime.now() - timedelta(seconds=10), - time_end=datetime.now() + timedelta(minutes=20), - subscription_id=None, + def _step_create_subscription(self, params: SubscriptionParams) -> Subscription: + self.begin_test_step("Create a subscription") + with self.check("Create subscription query succeeds", self._pid) as check: + try: + mutated_sub = self._dss.upsert_subscription(**params) + self.record_query(mutated_sub) + except QueryError as qe: + self.record_queries(qe.queries) + check.record_failed( + summary="Could not create subscription", + details=f"Failed to create subscription with error code {qe.cause_status_code}: {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + self.end_test_step() + return mutated_sub.subscription + + def _step_create_explicit_sub(self): + self._sub_params = self._planning_area.get_new_subscription_params( + subscription_id=self._sub_id, + start_time=datetime.now() - timedelta(seconds=10), + duration=timedelta(minutes=20), + notify_for_op_intents=True, + notify_for_constraints=False, ) + self._current_sub = self._step_create_subscription(self._sub_params) + def _step_create_oir(self, oir_params: PutOperationalIntentReferenceParameters): self.begin_test_step("Create an operational intent reference") + sub_id = oir_params.subscription_id if "subscription_id" in oir_params else None with self.check( "Create operational intent reference query succeeds", self._pid, @@ -120,6 +183,7 @@ def _step_create_oir(self): state=oir_params.state, base_url=oir_params.uss_base_url, oi_id=self._oir_id, + subscription_id=sub_id, ) self.record_query(query) self._current_oir = new_oir @@ -132,6 +196,172 @@ def _step_create_oir(self): ) self.end_test_step() + def _step_create_oir_insufficient_subscription(self): + self.begin_test_step( + "Provide subscription not covering extent of OIR being created" + ) + + oir_params = self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=datetime.now() - timedelta(seconds=10), + time_end=self._sub_params.end_time + + timedelta(seconds=1), # OIR ends 1 sec after subscription + subscription_id=self._sub_id, + ) + + with self.check( + "Request to create OIR with incorrect subscription fails", self._pid + ) as check: + try: + _, _, q = self._dss.put_op_intent( + extents=oir_params.extents, + key=oir_params.key, + state=oir_params.state, + base_url=oir_params.uss_base_url, + oi_id=self._oir_id, + subscription_id=oir_params.subscription_id, + ) + self.record_query(q) + # We don't expect to reach this point: + check.record_failed( + summary="OIR creation with too short subscription was not expected to succeed", + details=f"Was expecting an HTTP 400 response because of an insufficient subscription, but got {q.status_code} instead", + query_timestamps=[q.request.timestamp], + ) + except QueryError as qe: + self.record_queries(qe.queries) + if qe.cause_status_code == 400: + pass + else: + check.record_failed( + summary="OIR creation with too short subscription failed for unexpected reason", + details=f"Was expecting an HTTP 400 response because of an insufficient subscription, but got {qe.cause_status_code} instead", + query_timestamps=qe.query_timestamps, + ) + + self.end_test_step() + + def _step_update_oir_with_insufficient_explicit_sub(self): + # Create another subscription that is a few seconds short of covering the OIR: + oir_duration = ( + self._current_oir.time_end.value.datetime + - self._current_oir.time_start.value.datetime + ) + new_sub_params = self._planning_area.get_new_subscription_params( + subscription_id=self._extra_sub_id, + start_time=datetime.now() - timedelta(seconds=10), + duration=oir_duration - timedelta(seconds=2), + notify_for_op_intents=True, + notify_for_constraints=False, + ) + + self._current_extra_sub = self._step_create_subscription(new_sub_params) + + # Now attempt to mutate the OIR for it to use the invalid subscription: + oir_update_params = self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=self._current_oir.time_start.value.datetime, + time_end=self._current_oir.time_end.value.datetime, + subscription_id=self._extra_sub_id, + ) + + self.begin_test_step( + "Attempt to replace OIR's existing explicit subscription with an insufficient one" + ) + with self.check( + "Request to mutate OIR while providing an incorrect subscription fails", + self._pid, + ) as check: + try: + _, _, q = self._dss.put_op_intent( + extents=oir_update_params.extents, + key=oir_update_params.key, + state=oir_update_params.state, + base_url=oir_update_params.uss_base_url, + oi_id=self._oir_id, + subscription_id=oir_update_params.subscription_id, + ovn=self._current_oir.ovn, + ) + self.record_query(q) + # We don't expect to reach this point: + check.record_failed( + summary="OIR mutation with too short subscription was not expected to succeed", + details=f"Was expecting an HTTP 400 response because of an insufficient subscription, but got {q.status_code} instead", + query_timestamps=[q.request.timestamp], + ) + except QueryError as qe: + self.record_queries(qe.queries) + if qe.cause_status_code == 400: + pass + else: + check.record_failed( + summary="OIR mutation with too short subscription failed for unexpected reason", + details=f"Was expecting an HTTP 400 response because of an insufficient subscription, but got {qe.cause_status_code} instead. {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + self.end_test_step() + + def _step_update_oir_with_sufficient_explicit_sub(self): + self.begin_test_step("Replace the OIR's explicit subscription") + oir_update_params = self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=self._current_extra_sub.time_start.value.datetime, + time_end=self._current_extra_sub.time_end.value.datetime, + subscription_id=self._extra_sub_id, + ) + with self.check( + "Mutate operational intent reference query succeeds", + self._pid, + ) as check: + try: + mutated_oir, _, q = self._dss.put_op_intent( + extents=oir_update_params.extents, + key=oir_update_params.key, + state=oir_update_params.state, + base_url=oir_update_params.uss_base_url, + oi_id=self._oir_id, + subscription_id=oir_update_params.subscription_id, + ovn=self._current_oir.ovn, + ) + self.record_query(q) + self._current_oir = mutated_oir + except QueryError as qe: + self.record_queries(qe.queries) + check.record_failed( + summary="OIR mutation with correct subscription failed", + details=f"Was expecting an HTTP 200 response for a mutation with valid parameters, but got {qe.cause_status_code} instead. {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + self.end_test_step() + + def _step_oir_has_correct_subscription(self, expected_sub_id: SubscriptionID): + self.begin_test_step("OIR is attached to expected subscription") + 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, + ) + + with self.check("OIR is attached to expected subscription") as check: + if oir.subscription_id != expected_sub_id: + 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}", + ) + self.end_test_step() + def _step_attempt_delete_missing_ovn(self): self.begin_test_step("Attempt deletion with missing OVN") @@ -270,18 +500,24 @@ def _step_attempt_mutation_incorrect_ovn(self): self.end_test_step() - def _setup_case(self): - self.begin_test_case("Setup") + def _setup_case(self, create_oir=False, create_explicit_sub=False): # Multiple runs of the scenario seem to rely on the same instance of it: # thus we need to reset the state of the scenario before running it. self._current_oir = None + self._current_sub = None + self._current_extra_sub = None self.begin_test_step("Ensure clean workspace") self._ensure_clean_workspace_step() self.end_test_step() - self._step_create_oir() + if create_oir: + sub_id = self._sub_id if create_explicit_sub else None + self._step_create_oir( + oir_params=self._default_oir_params(subscription_id=sub_id) + ) - self.end_test_case() + if create_explicit_sub: + self._step_create_explicit_sub() def _ensure_clean_workspace_step(self): @@ -315,3 +551,15 @@ def _test_params_for_current_time(self): time_end=datetime.now() + timedelta(minutes=20), subscription_id=None, ) + + def _default_oir_params( + self, subscription_id: SubscriptionID + ) -> PutOperationalIntentReferenceParameters: + return self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=datetime.now() - timedelta(seconds=10), + time_end=datetime.now() + timedelta(minutes=20), + subscription_id=subscription_id, + ) From 6c9828aee8e9922e48ff5d4554c162f3463cd174 Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Tue, 15 Oct 2024 10:56:57 +0200 Subject: [PATCH 2/3] [uss_qualifier] oir_simple: check oir can be created without sub and attached to explicit sub --- .../oir/oir_has_expected_subscription.md | 7 + .../astm/utm/dss/op_intent_ref_simple.md | 53 +++++++- .../astm/utm/dss/op_intent_ref_simple.py | 126 +++++++++++++++--- 3 files changed, 170 insertions(+), 16 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md index 7c8bc15d78..b4cb2a2083 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/oir_has_expected_subscription.md @@ -8,3 +8,10 @@ Check query succeeds If the OIR returned by the DSS under test is not attached to the expected subscription, it is in violation of **[astm.f3548.v21.DSS0005,1](../../../../../../requirements/astm/f3548/v21.md)** + +## [Get referenced Subscription](../sub/crud/read_query.md) + +Attempt to fetch the subscription referenced by the OIR in order to confirm that it does not exist. + +This is only used in circumstances where the subscription is expected to not exist and the DSS implementation +is not using the _null_ subscription ID with value `00000000-0000-4000-8000-000000000000`. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md index 1fe6fc192c..667b407226 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md @@ -28,14 +28,65 @@ Verifies the behavior of a DSS for simple interactions pertaining to operational This step ensures that no entities with the known test IDs exists in the DSS. +## OIR in ACCEPTED state can be created without subscription test case + +Checks that a DSS allows an OIR to be created in the accepted state without any subscription. + +### [Create an operational intent reference test step](./fragments/oir/crud/create_query.md) + +This step verifies that an OIR can be created in the ACCEPTED state without providing any subscription information (implicit or explicit) in the request. + +### [OIR is not attached to any subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is not attached to any subscription. + +#### 🛑 Referenced subscription does not exist check + +If the placeholder subscription contained in the OIR returned to the qualifier by the DSS references an existing subscription, +then the DSS under test is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**, as the creation request +did not specify any subscription. + +## Validate explicit subscription being attached to OIR without subscription test case + +Ensures that an explicit subscription can be attached to an OIR without subscription attached, and that the subscription is required to properly cover the OIR. + ### [Create a subscription test step](./fragments/sub/crud/create_query.md) -Create an explicit subscription to be used in this scenario. +Create an explicit subscription to be used in this test cases. + +### Attempt to attach insufficient subscription to OIR test step + +This step verifies that the DSS refuses the request to attach an insufficient subscription to an OIR that currently has no subscription. + +#### 🛑 Request to attach insufficient subscription to OIR fails check + +If the DSS under test allows the qualifier to attach an insufficient explicit subscription to a subscription-free OIR, +it is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)** + +### [OIR is not attached to any subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is not attached to any subscription. + +### [Attach explicit subscription to OIR test step](./fragments/oir/crud/update_query.md) + +This step verifies that an explicit subscription covering the OIR can be attached to an OIR that currently has no subscription. + +### [OIR is attached to expected subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is attached to the subscription provided upon creation. ## Validate explicit subscription on OIR creation test case Ensures that the explicit subscription provided upon creation of an OIR is properly validated and attached to the OIR. +### [Ensure clean workspace test step](./clean_workspace.md) + +This step ensures that no entities with the known test IDs exists in the DSS. + +### [Create a subscription test step](./fragments/sub/crud/create_query.md) + +Create an explicit subscription to be used in this and the following test cases. + ### Provide subscription not covering extent of OIR being created test step This step verifies that an OIR cannot be created when an explicit subscription that does not cover the extent of the OIR is specified. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py index 30631dd50f..147bb67aa8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py @@ -34,6 +34,14 @@ ) from monitoring.uss_qualifier.suites.suite import ExecutionContext +# The official 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 OIRSimple(TestScenario): """ @@ -103,10 +111,37 @@ def __init__( def run(self, context: ExecutionContext): self.begin_test_scenario(context) self.begin_test_case("Setup") - self._setup_case(create_explicit_sub=True) + self._setup_case() + self.end_test_case() + + self.begin_test_case( + "OIR in ACCEPTED state can be created without subscription" + ) + self._step_create_oir( + oir_params=self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=datetime.now() - timedelta(seconds=10), + time_end=datetime.now() + timedelta(minutes=20), + subscription_id=None, + implicit_sub_base_url=None, + ), + ) + self._step_oir_has_correct_subscription(expected_sub_id=None) + self.end_test_case() + + self.begin_test_case( + "Validate explicit subscription being attached to OIR without subscription" + ) + self._step_update_oir_with_insufficient_explicit_sub(is_replacement=False) + self._step_oir_has_correct_subscription(expected_sub_id=None) + self._step_update_oir_with_sufficient_explicit_sub(is_replacement=False) + self._step_oir_has_correct_subscription(expected_sub_id=self._extra_sub_id) self.end_test_case() self.begin_test_case("Validate explicit subscription on OIR creation") + self._setup_case(create_explicit_sub=True) self._step_create_oir_insufficient_subscription() self._step_create_oir( oir_params=self._planning_area.get_new_operational_intent_ref_params( @@ -124,9 +159,9 @@ def run(self, context: ExecutionContext): self.begin_test_case( "Validate explicit subscription upon subscription replacement" ) - self._step_update_oir_with_insufficient_explicit_sub() + self._step_update_oir_with_insufficient_explicit_sub(is_replacement=True) self._step_oir_has_correct_subscription(expected_sub_id=self._sub_id) - self._step_update_oir_with_sufficient_explicit_sub() + self._step_update_oir_with_sufficient_explicit_sub(is_replacement=True) self._step_oir_has_correct_subscription(expected_sub_id=self._extra_sub_id) self.end_test_case() @@ -177,6 +212,10 @@ def _step_create_oir(self, oir_params: PutOperationalIntentReferenceParameters): self._pid, ) as check: try: + no_implicit_sub = ( + "new_subscription" not in oir_params + or "uss_base_url" not in oir_params.new_subscription + ) new_oir, subs, query = self._dss.put_op_intent( extents=oir_params.extents, key=oir_params.key, @@ -184,6 +223,7 @@ def _step_create_oir(self, oir_params: PutOperationalIntentReferenceParameters): base_url=oir_params.uss_base_url, oi_id=self._oir_id, subscription_id=sub_id, + force_no_implicit_subscription=no_implicit_sub, ) self.record_query(query) self._current_oir = new_oir @@ -243,7 +283,7 @@ def _step_create_oir_insufficient_subscription(self): self.end_test_step() - def _step_update_oir_with_insufficient_explicit_sub(self): + def _step_update_oir_with_insufficient_explicit_sub(self, is_replacement: bool): # Create another subscription that is a few seconds short of covering the OIR: oir_duration = ( self._current_oir.time_end.value.datetime @@ -268,12 +308,19 @@ def _step_update_oir_with_insufficient_explicit_sub(self): time_end=self._current_oir.time_end.value.datetime, subscription_id=self._extra_sub_id, ) - - self.begin_test_step( + step_name = ( "Attempt to replace OIR's existing explicit subscription with an insufficient one" + if is_replacement + else "Attempt to attach insufficient subscription to OIR" + ) + self.begin_test_step(step_name) + check_name = ( + "Request to mutate OIR while providing an incorrect subscription fails" + if is_replacement + else "Request to attach insufficient subscription to OIR fails" ) with self.check( - "Request to mutate OIR while providing an incorrect subscription fails", + check_name, self._pid, ) as check: try: @@ -289,7 +336,7 @@ def _step_update_oir_with_insufficient_explicit_sub(self): self.record_query(q) # We don't expect to reach this point: check.record_failed( - summary="OIR mutation with too short subscription was not expected to succeed", + summary="Request for OIR with too short subscription was not expected to succeed", details=f"Was expecting an HTTP 400 response because of an insufficient subscription, but got {q.status_code} instead", query_timestamps=[q.request.timestamp], ) @@ -299,14 +346,19 @@ def _step_update_oir_with_insufficient_explicit_sub(self): pass else: check.record_failed( - summary="OIR mutation with too short subscription failed for unexpected reason", + summary="Request for OIR with too short subscription failed for unexpected reason", details=f"Was expecting an HTTP 400 response because of an insufficient subscription, but got {qe.cause_status_code} instead. {qe.msg}", query_timestamps=qe.query_timestamps, ) self.end_test_step() - def _step_update_oir_with_sufficient_explicit_sub(self): - self.begin_test_step("Replace the OIR's explicit subscription") + def _step_update_oir_with_sufficient_explicit_sub(self, is_replacement: bool): + step_name = ( + "Replace the OIR's explicit subscription" + if is_replacement + else "Attach explicit subscription to OIR" + ) + self.begin_test_step(step_name) oir_update_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, @@ -340,8 +392,15 @@ def _step_update_oir_with_sufficient_explicit_sub(self): ) self.end_test_step() - def _step_oir_has_correct_subscription(self, expected_sub_id: SubscriptionID): - self.begin_test_step("OIR is attached to expected subscription") + def _step_oir_has_correct_subscription( + self, expected_sub_id: Optional[SubscriptionID] + ): + step_check_name = ( + "OIR is attached to expected subscription" + if expected_sub_id + else "OIR is not attached to any 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) @@ -354,11 +413,48 @@ def _step_oir_has_correct_subscription(self, expected_sub_id: SubscriptionID): query_timestamps=qe.query_timestamps, ) - with self.check("OIR is attached to expected subscription") as check: - if oir.subscription_id != expected_sub_id: + sub_is_as_expected = False + referenced_sub_was_found_when_non_expected = False + if expected_sub_id is None: + # The official DSS implementation will set the subscription ID to 00000000-0000-4000-8000-000000000000 when the OIR is not attached to any subscription. + # Other implementations may use a different value, as the OpenAPI spec does not allow the value to be empty + # We 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 strictly compliant with the spec, but acceptable in this context + 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("Get referenced Subscription") 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 + + with self.check("OIR is attached to expected subscription", 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() From df0d979454a98b73c2caff9f93288ea03c96f307 Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Tue, 15 Oct 2024 12:45:57 +0200 Subject: [PATCH 3/3] [uss_qualifier] oir_simple: accepted oir can be detached from explicit subscription --- .../astm/utm/dss/op_intent_ref_simple.md | 12 ++++++ .../astm/utm/dss/op_intent_ref_simple.py | 41 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md index 667b407226..afa59473ac 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md @@ -135,6 +135,18 @@ to another explicit subscription that properly covers the extent of the OIR. This step verifies that the OIR is attached to the subscription provided upon mutation. +## Remove explicit subscription from OIR test case + +Checks that an OIR in the ACCEPTED state that is attached to an explicit subscription can be mutated in order to not be attached to any subscription. + +### [Remove explicit subscription from OIR test step](./fragments/oir/crud/update_query.md) + +This step verifies that an OIR attached to an explicit subscription can be mutated in order to not be attached to any subscription. + +### [OIR is not attached to any subscription test step](./fragments/oir/oir_has_expected_subscription.md) + +This step verifies that the OIR is not attached to any subscription. + ## Deletion requires correct OVN test case Ensures that a DSS will only delete OIRs when the correct OVN is presented. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py index 147bb67aa8..3818f3165d 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py @@ -165,6 +165,11 @@ def run(self, context: ExecutionContext): self._step_oir_has_correct_subscription(expected_sub_id=self._extra_sub_id) self.end_test_case() + self.begin_test_case("Remove explicit subscription from OIR") + self._step_remove_subscription_from_oir() + self._step_oir_has_correct_subscription(expected_sub_id=None) + self.end_test_case() + self.begin_test_case("Deletion requires correct OVN") self._setup_case(create_oir=True) self._step_attempt_delete_missing_ovn() @@ -392,6 +397,42 @@ def _step_update_oir_with_sufficient_explicit_sub(self, is_replacement: bool): ) self.end_test_step() + def _step_remove_subscription_from_oir(self): + self.begin_test_step("Remove explicit subscription from OIR") + oir_update_params = self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=OperationalIntentState.Accepted, + uss_base_url=self._planning_area.get_base_url(), + time_start=self._current_oir.time_start.value.datetime, + time_end=self._current_oir.time_end.value.datetime, + subscription_id=None, + ) + with self.check( + "Mutate operational intent reference query succeeds", + self._pid, + ) as check: + try: + mutated_oir, _, q = self._dss.put_op_intent( + extents=oir_update_params.extents, + key=oir_update_params.key, + state=oir_update_params.state, + base_url=oir_update_params.uss_base_url, + oi_id=self._oir_id, + subscription_id=None, + ovn=self._current_oir.ovn, + force_no_implicit_subscription=True, + ) + self.record_query(q) + self._current_oir = mutated_oir + except QueryError as qe: + self.record_queries(qe.queries) + check.record_failed( + summary="Removal of explicit subscription from OIR failed", + details=f"Was expecting an HTTP 200 response for a mutation with valid parameters, but got {qe.cause_status_code} instead. {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + self.end_test_step() + def _step_oir_has_correct_subscription( self, expected_sub_id: Optional[SubscriptionID] ):