Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[uss_qualifier/resources/f3548/dss/find_op_intent] Use QueryError for error handling #496

Merged
merged 1 commit into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down
32 changes: 18 additions & 14 deletions monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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],
)
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
)

Expand All @@ -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],
)

Expand Down Expand Up @@ -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],
)

Expand Down
14 changes: 6 additions & 8 deletions monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
53 changes: 30 additions & 23 deletions monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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):
Expand All @@ -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
}
Expand All @@ -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.

Expand Down
Loading