diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/sub/unchanged_after_conflict.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/sub/unchanged_after_conflict.md new file mode 100644 index 0000000000..63d04f5ee7 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/sub/unchanged_after_conflict.md @@ -0,0 +1,19 @@ +# Subscriptions unchanged after rejected request test step fragment + +This test step fragment validates that a request to create or mutate an operational intent reference that was rejected +because of a conflict did not create, modify or delete any subscription, including implicit subscriptions. + +The subscriptions owned by the client within the planning area are queried before and after the rejected request, and compared. + +## 🛑 Successful subscription search query check + +**[astm.f3548.v21.DSS0005,5](../../../../../../requirements/astm/f3548/v21.md)** requires the implementation of the DSS endpoint to allow callers to retrieve the subscriptions they created. + +## 🛑 Rejected request did not create or modify subscriptions check + +A request to create or mutate an operational intent reference that is rejected because of missing OVNs is not expected to have any effect. +In particular, the DSS is not expected to create an implicit subscription requested by such a request, nor to expand +an existing implicit subscription to cover the rejected operational intent reference. + +If the subscriptions owned by the client differ before and after the rejected request, the DSS fails to properly manage +implicit subscriptions and 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_key_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md index 135667b05f..41ef72772c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md @@ -94,6 +94,8 @@ This step will validate the response body for the `HTTP 409` error response from Checks that an attempt to create an OIR without specifying the OVNs of the already existing and overlapping OIRs fails. +#### [Subscriptions are unchanged](fragments/sub/unchanged_after_conflict.md) + ### Attempt valid OIR creation overlapping with both OIRs test step This test step will attempt to create an operational intent reference that intersects with both of the previously created OIRs, @@ -141,4 +143,17 @@ The expectation is that the DSS will require the missing OVN. #### [Non de-conflicted mutation request fails](fragments/oir/crud/update_conflict.md) +### Attempt mutation requiring expansion of the implicit subscription test step + +This test step attempts to mutate the third operational intent reference so that it ends 10 minutes later, while: +- specifying, as its subscription, the implicit subscription that was created along with it, and +- omitting the OVNs of the two other operational intent references in the `key` field. + +The mutated operational intent reference would end after its implicit subscription, which the DSS would therefore need to expand. +Because the OVNs are missing, the DSS is expected to reject the request, and to leave the implicit subscription unchanged. + +#### [Non de-conflicted mutation request fails](fragments/oir/crud/update_conflict.md) + +#### [Subscriptions are unchanged](fragments/sub/unchanged_after_conflict.md) + ## [Cleanup](./clean_workspace_op_intents_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py index 79ba67316e..f80368d9c3 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py @@ -10,6 +10,7 @@ from monitoring.monitorlib import fetch, schema_validation from monitoring.monitorlib.fetch import QueryError +from monitoring.monitorlib.fetch.scd import FetchedSubscriptions from monitoring.monitorlib.schema_validation import F3548_21 from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource @@ -218,6 +219,7 @@ def _attempt_update_expect_conflict( base_url=oir_params.uss_base_url, oi_id=oir_id, ovn=ovn, + subscription_id=oir_params.subscription_id, ) self.record_query(q) check.record_failed( @@ -280,9 +282,11 @@ def _steps_attempt_create_overlapping_oir(self): ) self.begin_test_step("Attempt OIR creation overlapping with both OIRs") + subs_before = self._query_own_subs() self._attempt_creation_expect_conflict( third_oir_id, conflict_both, [first_oir.id, second_oir.id] ) + self._check_subscriptions_unchanged(subs_before) self.end_test_step() # Finally, we create the third OIR with the correct key, so we may try to mutate it at the next step @@ -404,6 +408,66 @@ def _steps_attempt_mutation_to_cause_overlap(self): ) self.end_test_step() + # Try to extend the third OIR beyond the end of the second one without providing the conflicting OVNs + expand_implicit_sub = self._planning_area.get_new_operational_intent_ref_params( + key=[], + state=oir_to_mutate.state, + uss_base_url=oir_to_mutate.uss_base_url, + time_start=oir_to_mutate.time_start.value.datetime, + time_end=oir_to_mutate.time_end.value.datetime + timedelta(minutes=10), + subscription_id=oir_to_mutate.subscription_id, + ) + + self.begin_test_step( + "Attempt mutation requiring expansion of the implicit subscription" + ) + subs_before = self._query_own_subs() + self._attempt_update_expect_conflict( + oir_to_mutate.id, + expand_implicit_sub, + [first_oir.id, second_oir.id], + oir_to_mutate.ovn, + ) + self._check_subscriptions_unchanged(subs_before) + self.end_test_step() + + def _query_own_subs(self) -> FetchedSubscriptions: + """Queries the subscriptions owned by the client in the planning area.""" + query = self._dss.query_subscriptions( + self._planning_area_volume4d.to_f3548v21() + ) + self.record_query(query) + with self.check("Successful subscription search query", self._pid) as check: + if query.status_code != 200: + check.record_failed( + summary="Could not query subscriptions", + details=f"When attempting to query subscriptions from the DSS, received {query.status_code}", + query_timestamps=[query.request.timestamp], + ) + return query + + def _check_subscriptions_unchanged(self, query_before: FetchedSubscriptions): + """Checks that the subscriptions owned by the client are the same as before a rejected request.""" + query = self._query_own_subs() + subs_before = {s.id: s.version for s in query_before.subscriptions.values()} + subs_after = {s.id: s.version for s in query.subscriptions.values()} + with self.check( + "Rejected request did not create or modify subscriptions", self._pid + ) as check: + created = subs_after.keys() - subs_before.keys() + deleted = subs_before.keys() - subs_after.keys() + modified = { + sub_id + for sub_id in subs_before.keys() & subs_after.keys() + if subs_before[sub_id] != subs_after[sub_id] + } + if created or deleted or modified: + check.record_failed( + summary="Subscriptions changed by rejected request", + details=f"The request was rejected because of a conflict, but subscriptions were changed: created {created}, deleted {deleted}, modified {modified}", + query_timestamps=[query.request.timestamp], + ) + def _setup_case(self): self.begin_test_case("Setup") self.begin_test_step("Ensure clean workspace")