From 87a61945896225b6671d6363f25c90da6f39042f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Fri, 9 Feb 2024 03:41:53 +0100 Subject: [PATCH] [uss_qualifier/resources/f3548/dss/find_op_intent] Use QueryError for error handling (#496) --- .../resources/astm/f3548/v21/dss.py | 16 ++++-- .../astm/utm/dss_interoperability.py | 32 ++++++----- .../astm/utm/off_nominal_planning/down_uss.py | 21 +++++--- .../astm/utm/op_intent_ref_access_control.py | 42 +++++++++------ .../scenarios/astm/utm/prep_planners.py | 14 +++-- .../scenarios/astm/utm/test_steps.py | 53 +++++++++++-------- 6 files changed, 105 insertions(+), 73 deletions(-) diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py index 520914a059..34764a36a5 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -118,6 +118,11 @@ def with_different_auth( def find_op_intent( self, extent: Volume4D ) -> Tuple[List[OperationalIntentReference], Query]: + """ + Find operational intents overlapping with a given volume 4D. + Raises: + * QueryError: if request failed, if HTTP status code is different than 200, or if the parsing of the response failed. + """ self._uses_scope(Scope.StrategicCoordination) op = OPERATIONS[OperationID.QueryOperationalIntentReferences] req = QueryOperationalIntentReferenceParameters(area_of_interest=extent) @@ -131,12 +136,13 @@ def find_op_intent( json=req, ) if query.status_code != 200: - result = None + raise QueryError( + f"Received code {query.status_code} when attempting to find operational intents in {extent}", + query, + ) else: - result = ImplicitDict.parse( - query.response.json, QueryOperationalIntentReferenceResponse - ).operational_intent_references - return result, query + result = query.parse_json_result(QueryOperationalIntentReferenceResponse) + return result.operational_intent_references, query def get_op_intent_reference( self, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py index e465088ef4..e769466ace 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py @@ -3,6 +3,7 @@ from typing import List from urllib.parse import urlparse +from monitoring.monitorlib.fetch import QueryError from monitoring.uss_qualifier.resources.astm.f3548.v21 import PlanningAreaResource from monitoring.uss_qualifier.suites.suite import ExecutionContext from uas_standards.astm.f3548.v21.api import Volume4D, Volume3D, Polygon, LatLngPoint @@ -75,18 +76,21 @@ def _test_env_reqs(self): details=f"DSS (URL: {dss.base_url}, netloc: {parsed_url.netloc}, resolved IP: {ip_addr}) is not publicly addressable", ) - # dummy search query - _, q = dss.find_op_intent(extent=self._valid_search_area) - self.record_query(q) - with self.check("DSS instance is reachable", [dss.participant_id]) as check: - # status code 999 means we could not even get a valid HTTP reply, - # either from a time-out or an un-routable address, implying the DSS is likely unavailable. - # if the code is anything else than 999, we got an actual HTTP reply, and as far as this - # scenario is concerned, the DSS is available. - if q.status_code == 999: - check.record_failed( - summary=f"Could not reach DSS instance", - details=f"{q.response.get('content', '')}", - query_timestamps=[q.request.timestamp], - ) + try: + # dummy search query + _, q = dss.find_op_intent(extent=self._valid_search_area) + self.record_query(q) + except QueryError as e: + self.record_queries(e.queries) + q = e.queries[0] + # status code 999 means we could not even get a valid HTTP reply, + # either from a time-out or an un-routable address, implying the DSS is likely unavailable. + # if the code is anything else than 999, we got an actual HTTP reply, and as far as this + # scenario is concerned, the DSS is available. + if q.status_code == 999: + check.record_failed( + summary=f"Could not reach DSS instance", + details=f"{q.response.get('content', '')}; {e}", + query_timestamps=[q.request.timestamp], + ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py index 3ad7a5840b..f64a1e6368 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py @@ -135,12 +135,16 @@ def run(self, context: ExecutionContext): def _setup(self): self.begin_test_step("Resolve USS ID of virtual USS") - _, dummy_query = self.dss.find_op_intent(self._intents_extent) with self.check("Successful dummy query", [self.dss.participant_id]) as check: - if dummy_query.status_code != 200: + try: + _, dummy_query = self.dss.find_op_intent(self._intents_extent) + self.record_query(dummy_query) + except QueryError as e: + self.record_queries(e.queries) + dummy_query = e.queries[0] check.record_failed( summary="Failed to query DSS", - details=f"DSS responded code {dummy_query.status_code}; error message: {dummy_query.error_message}", + details=f"DSS responded code {dummy_query.status_code}; error message: {dummy_query.error_message}; {e}", query_timestamps=[dummy_query.request.timestamp], ) self.uss_qualifier_sub = self.dss.client.auth_adapter.get_sub() @@ -272,16 +276,19 @@ def _plan_flight_conflict_planned(self): self.end_test_step() def _clear_op_intents(self): - oi_refs, find_query = self.dss.find_op_intent(self._intents_extent) - self.record_query(find_query) with self.check( "Successful operational intents cleanup", [self.dss.participant_id] ) as check: - if find_query.status_code != 200: + try: + oi_refs, find_query = self.dss.find_op_intent(self._intents_extent) + self.record_query(find_query) + except QueryError as e: + self.record_queries(e.queries) + find_query = e.queries[0] check.record_failed( summary=f"Failed to query operational intent references from DSS in {self._intents_extent} for cleanup", - details=f"DSS responded code {find_query.status_code}; error message: {find_query.error_message}", + details=f"DSS responded code {find_query.status_code}; error message: {find_query.error_message}; {e}", query_timestamps=[find_query.request.timestamp], ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_ref_access_control.py b/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_ref_access_control.py index 41dc538b35..8b575bb3d4 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_ref_access_control.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_ref_access_control.py @@ -198,17 +198,21 @@ def _clean_known_op_intents_ids(self): def _attempt_to_delete_remaining_op_intents(self): """Search for op intents and attempt to delete them using the main credentials""" - # Also check for any potential other op_intents and delete them - (op_intents_1, q) = self._dss.find_op_intent(self._intents_extent) - self.record_query(q) + with self.check( "Operational intent references can be searched for", self._pid, ) as check: - if q.response.status_code != 200: + try: + # Also check for any potential other op_intents and delete them + (op_intents_1, q) = self._dss.find_op_intent(self._intents_extent) + self.record_query(q) + except QueryError as e: + self.record_queries(e.queries) + q = e.queries[0] check.record_failed( f"Could not search operational intent references using main credentials", - details=f"DSS responded with {q.response.status_code} to attempt to search OIs", + details=f"DSS responded with {q.response.status_code} to attempt to search OIs; {e}", query_timestamps=[q.request.timestamp], ) @@ -232,18 +236,21 @@ def _attempt_to_delete_remaining_op_intents(self): query_timestamps=[dq.request.timestamp], ) - (op_intents_2, q) = self._dss_separate_creds.find_op_intent( - self._intents_extent - ) - self.record_query(q) with self.check( "Operational intent references can be searched for", self._pid, ) as check: - if q.response.status_code != 200: + try: + (op_intents_2, q) = self._dss_separate_creds.find_op_intent( + self._intents_extent + ) + self.record_query(q) + except QueryError as e: + self.record_queries(e.queries) + q = e.queries[0] check.record_failed( f"Could not search operational intent references using second credentials", - details=f"DSS responded with {q.response.status_code} to attempt to search OIs", + details=f"DSS responded with {q.response.status_code} to attempt to search OIs; {e}", query_timestamps=[q.request.timestamp], ) @@ -288,17 +295,20 @@ def _ensure_clean_workspace(self) -> bool: # Search and attempt deleting what may be found through search self._attempt_to_delete_remaining_op_intents() - # We can't delete anything that would be left. - (stray_oir, q) = self._dss.find_op_intent(self._intents_extent) - self.record_query(q) with self.check( "Operational intent references can be searched for", self._pid, ) as check: - if q.response.status_code != 200: + try: + # We can't delete anything that would be left. + (stray_oir, q) = self._dss.find_op_intent(self._intents_extent) + self.record_query(q) + except QueryError as e: + self.record_queries(e.queries) + q = e.queries[0] check.record_failed( f"Could not search operational intent references using main credentials", - details=f"DSS responded with {q.response.status_code} to attempt to search OIs", + details=f"DSS responded with {q.response.status_code} to attempt to search OIs; {e}", query_timestamps=[q.request.timestamp], ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py b/monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py index e449608746..1a10fa92c6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py @@ -1,5 +1,6 @@ from typing import Optional, List +from monitoring.monitorlib.fetch import QueryError from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance from monitoring.uss_qualifier.resources.flight_planning import ( @@ -92,16 +93,13 @@ def _validate_clear_area( with self.check("DSS responses", [self.dss.participant_id]) as check: try: op_intents, query = self.dss.find_op_intent(area.to_f3548v21()) - except ValueError as e: - check.record_failed( - summary="Error parsing DSS response", - details=str(e), - ) - self.record_query(query) - if op_intents is None: + self.record_query(query) + except QueryError as e: + self.record_queries(e.queries) + query = e.queries[0] check.record_failed( summary="Error querying DSS for operational intents", - details="See query", + details=f"See query; {e}", query_timestamps=[query.request.timestamp], ) found_intents.extend(op_intents) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py index d3edc1aa0f..700c1cf2f8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py @@ -16,6 +16,7 @@ UasState, AirspaceUsageState, ) +from monitoring.monitorlib.fetch import QueryError from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance from monitoring.uss_qualifier.resources.flight_planning.flight_planner import ( @@ -71,9 +72,20 @@ def __init__( self._orig_oi_ref: Optional[OperationalIntentReference] = orig_oi_ref def __enter__(self) -> OpIntentValidator: - self._before_oi_refs, self._before_query = self._dss.find_op_intent( - self._extent - ) + with self._scenario.check("DSS responses", [self._dss.participant_id]) as check: + try: + self._before_oi_refs, self._before_query = self._dss.find_op_intent( + self._extent + ) + self._scenario.record_query(self._before_query) + except QueryError as e: + self._scenario.record_queries(e.queries) + self._before_query = e.queries[0] + check.record_failed( + summary="Failed to query DSS for operational intent references before planning request", + details=f"Received status code {self._before_query.status_code} from the DSS; {e}", + query_timestamps=[self._before_query.request.timestamp], + ) return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -89,7 +101,21 @@ def _find_after_oi(self, oi_id: str) -> Optional[OperationalIntentReference]: return found[0] if len(found) != 0 else None def _begin_step_fragment(self): - self._after_oi_refs, self._after_query = self._dss.find_op_intent(self._extent) + with self._scenario.check("DSS responses", [self._dss.participant_id]) as check: + try: + self._after_oi_refs, self._after_query = self._dss.find_op_intent( + self._extent + ) + self._scenario.record_query(self._after_query) + except QueryError as e: + self._scenario.record_queries(e.queries) + self._after_query = e.queries[0] + check.record_failed( + summary="Failed to query DSS for operational intent references after planning request", + details=f"Received status code {self._after_query.status_code} from the DSS; {e}", + query_timestamps=[self._after_query.request.timestamp], + ) + oi_ids_delta = {oi_ref.id for oi_ref in self._after_oi_refs} - { oi_ref.id for oi_ref in self._before_oi_refs } @@ -103,25 +129,6 @@ def _begin_step_fragment(self): if len(oi_ids_delta) == 1: self._new_oi_ref = self._find_after_oi(oi_ids_delta.pop()) - self._scenario.record_query(self._before_query) - self._scenario.record_query(self._after_query) - - with self._scenario.check("DSS responses", [self._dss.participant_id]) as check: - if self._before_query.status_code != 200: - check.record_failed( - summary="Failed to query DSS for operational intent references before planning request", - severity=Severity.High, - details=f"Received status code {self._before_query.status_code} from the DSS", - query_timestamps=[self._before_query.request.timestamp], - ) - if self._after_query.status_code != 200: - check.record_failed( - summary="Failed to query DSS for operational intent references after planning request", - severity=Severity.High, - details=f"Received status code {self._after_query.status_code} from the DSS", - query_timestamps=[self._after_query.request.timestamp], - ) - def expect_removed(self, oi_id: EntityID) -> None: """Validate that a specific operational intent reference was removed from the DSS.