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, + )