diff --git a/monitoring/monitorlib/fetch/__init__.py b/monitoring/monitorlib/fetch/__init__.py index d69a3eb321..061337a799 100644 --- a/monitoring/monitorlib/fetch/__init__.py +++ b/monitoring/monitorlib/fetch/__init__.py @@ -73,10 +73,14 @@ def describe_flask_request(request: flask.Request) -> RequestDescription: "received_at": StringBasedDateTime(datetime.datetime.utcnow()), "headers": headers, } - try: - kwargs["json"] = request.json - except ValueError: - kwargs["body"] = request.data.decode("utf-8") + data = request.data.decode("utf-8") + if request.is_json: + try: + kwargs["json"] = json.loads(data) + except ValueError: + kwargs["body"] = data + else: + kwargs["body"] = data return RequestDescription(**kwargs) @@ -263,6 +267,14 @@ def status_code(self) -> int: def json_result(self) -> Optional[Dict]: return self.response.json + @property + def error_message(self) -> Optional[str]: + return ( + self.json_result["message"] + if self.json_result is not None and "message" in self.json_result + else None + ) + class QueryError(RuntimeError): """Error encountered when interacting with a server in the UTM ecosystem.""" diff --git a/monitoring/monitorlib/geo.py b/monitoring/monitorlib/geo.py index f568e0d0d3..b47b483bb2 100644 --- a/monitoring/monitorlib/geo.py +++ b/monitoring/monitorlib/geo.py @@ -7,6 +7,7 @@ from implicitdict import ImplicitDict import numpy as np import s2sphere +from s2sphere import LatLng from scipy.interpolate import RectBivariateSpline as Spline import shapely.geometry from uas_standards.astm.f3548.v21 import api as f3548v21 @@ -501,3 +502,38 @@ def egm96_geoid_offset(p: s2sphere.LatLng) -> float: # listed -90 to 90. Since latitude data are symmetric, we can simply # convert "-90 to 90" to "90 to -90" by inverting the requested latitude. return _egm96.ev(-lat, lng) + + +def generate_slight_overlap_area(in_points: List[LatLng]) -> List[LatLng]: + """ + Takes a list of LatLng points and returns a list of LatLng points that represents + a polygon only slightly overlapping with the input, and that is roughly half the diameter of the input. + + The returned polygon is built from the first point of the input, from which a square + is drawn in the direction opposite of the center of the input polygon. + + """ + overlap_corner = in_points[0] # the spot that will have a tiny overlap + + # Compute the center of mass of the input polygon + center = LatLng.from_degrees( + sum([point.lat().degrees for point in in_points]) / len(in_points), + sum([point.lng().degrees for point in in_points]) / len(in_points), + ) + + delta_lat = center.lat().degrees - overlap_corner.lat().degrees + delta_lng = center.lng().degrees - overlap_corner.lng().degrees + + same_lat_point = LatLng.from_degrees( + overlap_corner.lat().degrees, overlap_corner.lng().degrees - delta_lng + ) + same_lng_point = LatLng.from_degrees( + overlap_corner.lat().degrees - delta_lat, overlap_corner.lng().degrees + ) + + opposite_corner = LatLng.from_degrees( + overlap_corner.lat().degrees - delta_lat, + overlap_corner.lng().degrees - delta_lng, + ) + + return [overlap_corner, same_lat_point, opposite_corner, same_lng_point] diff --git a/monitoring/monitorlib/geo_test.py b/monitoring/monitorlib/geo_test.py new file mode 100644 index 0000000000..ba2b52af17 --- /dev/null +++ b/monitoring/monitorlib/geo_test.py @@ -0,0 +1,36 @@ +from typing import List, Tuple + +from s2sphere import LatLng + +from monitoring.monitorlib.geo import generate_slight_overlap_area + + +def _points(in_points: List[Tuple[float, float]]) -> List[LatLng]: + return [LatLng.from_degrees(*p) for p in in_points] + + +def test_generate_slight_overlap_area(): + # Square around 0,0 of edge length 2 -> first corner at 1,1 -> expect a square with overlapping corner at 1,1 + assert generate_slight_overlap_area( + _points([(1, 1), (1, -1), (-1, -1), (-1, 1)]) + ) == _points([(1, 1), (1, 2), (2, 2), (2, 1)]) + + # Square with diagonal from 0,0 to 1,1 -> first corner at 1,1 -> expect a square with overlapping corner at 1,1 + assert generate_slight_overlap_area( + _points([(1, 1), (0, 1), (0, 0), (1, 0)]) + ) == _points([(1, 1), (1, 1.5), (1.5, 1.5), (1.5, 1)]) + + # Square with diagonal from 0,0 to -1,-1 -> first corner at -1,-1 -> expect a square with overlapping corner at -1,-1 + assert generate_slight_overlap_area( + _points([(-1, -1), (0, -1), (0, 0), (-1, 0)]) + ) == _points([(-1, -1), (-1, -1.5), (-1.5, -1.5), (-1.5, -1)]) + + # Square with diagonal from 0,0 to -1,1 -> first corner at -1,1 -> expect a square with overlapping corner at -1,0 + assert generate_slight_overlap_area( + _points([(-1, 1), (-1, 0), (0, 0), (0, 1)]) + ) == _points([(-1, 1), (-1, 1.5), (-1.5, 1.5), (-1.5, 1)]) + + # Square with diagonal from 0,0 to 1,-1 -> first corner at 1,-1 -> expect a square with overlapping corner at 1,-1 + assert generate_slight_overlap_area( + _points([(1, -1), (1, 0), (0, 0), (0, -1)]) + ) == _points([(1, -1), (1, -1.5), (1.5, -1.5), (1.5, -1)]) diff --git a/monitoring/monitorlib/infrastructure.py b/monitoring/monitorlib/infrastructure.py index 093ef73c28..6b88b9d515 100644 --- a/monitoring/monitorlib/infrastructure.py +++ b/monitoring/monitorlib/infrastructure.py @@ -3,7 +3,7 @@ import functools from typing import Dict, List, Optional import urllib.parse -from aiohttp import ClientSession +from aiohttp import ClientSession, ClientResponse import jwt import requests @@ -190,32 +190,52 @@ def adjust_request_kwargs(self, url, method, kwargs): return kwargs async def put(self, url, **kwargs): + """Returns (status, headers, json)""" url = self._prefix_url + url if "auth" not in kwargs: kwargs = self.adjust_request_kwargs(url, "PUT", kwargs) async with self._client.put(url, **kwargs) as response: - return response.status, await response.json() + return ( + response.status, + {k: v for k, v in response.headers.items()}, + await response.json(), + ) async def get(self, url, **kwargs): + """Returns (status, headers, json)""" url = self._prefix_url + url if "auth" not in kwargs: kwargs = self.adjust_request_kwargs(url, "GET", kwargs) async with self._client.get(url, **kwargs) as response: - return response.status, await response.json() + return ( + response.status, + {k: v for k, v in response.headers.items()}, + await response.json(), + ) async def post(self, url, **kwargs): + """Returns (status, headers, json)""" url = self._prefix_url + url if "auth" not in kwargs: kwargs = self.adjust_request_kwargs(url, "POST", kwargs) async with self._client.post(url, **kwargs) as response: - return response.status, await response.json() + return ( + response.status, + {k: v for k, v in response.headers.items()}, + await response.json(), + ) async def delete(self, url, **kwargs): + """Returns (status, headers, json)""" url = self._prefix_url + url if "auth" not in kwargs: kwargs = self.adjust_request_kwargs(url, "DELETE", kwargs) async with self._client.delete(url, **kwargs) as response: - return response.status, await response.json() + return ( + response.status, + {k: v for k, v in response.headers.items()}, + await response.json(), + ) def default_scopes(scopes: List[str]): diff --git a/monitoring/prober/infrastructure.py b/monitoring/prober/infrastructure.py index 5a5f145965..d0124b10c6 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: 373 +# Next code: 374 def register_resource_type(code: int, description: str) -> ResourceType: """Register that the specified code refers to the described resource. diff --git a/monitoring/prober/rid/v1/test_isa_simple_heavy_traffic_concurrent.py b/monitoring/prober/rid/v1/test_isa_simple_heavy_traffic_concurrent.py index d22d26b07e..b85e59cae9 100644 --- a/monitoring/prober/rid/v1/test_isa_simple_heavy_traffic_concurrent.py +++ b/monitoring/prober/rid/v1/test_isa_simple_heavy_traffic_concurrent.py @@ -107,8 +107,8 @@ def test_create_isa_concurrent(ids, session_ridv1_async): ) ) for isa_id, resp in results: - assert resp[0] == 200, resp[1] - data = resp[1] + assert resp[0] == 200, resp[2] + data = resp[2] assert data["service_area"]["id"] == isa_id assert data["service_area"]["flights_url"] == "https://example.com/dss" assert_datetimes_are_equal( @@ -133,9 +133,9 @@ def test_get_isa_by_ids_concurrent(ids, session_ridv1_async): ) ) for isa_id, resp in results: - assert resp[0] == 200, resp[1] + assert resp[0] == 200, resp[2] - data = resp[1] + data = resp[2] assert data["service_area"]["id"] == isa_id assert data["service_area"]["flights_url"] == FLIGHTS_URL @@ -162,8 +162,8 @@ def test_delete_isa_concurrent(ids, session_ridv1_async): ) for isa_id, resp in results: - assert resp[0] == 200, resp[1] - version = resp[1]["service_area"]["version"] + assert resp[0] == 200, resp[2] + version = resp[2]["service_area"]["version"] version_map[isa_id] = version # Delete ISAs concurrently @@ -178,4 +178,4 @@ def test_delete_isa_concurrent(ids, session_ridv1_async): ) for isa_id, resp in results: - assert resp[0], resp[1] + assert resp[0], resp[2] diff --git a/monitoring/prober/scd/test_operation_simple_heavy_traffic_concurrent.py b/monitoring/prober/scd/test_operation_simple_heavy_traffic_concurrent.py index 8a9d8d21e0..a885e7469a 100644 --- a/monitoring/prober/scd/test_operation_simple_heavy_traffic_concurrent.py +++ b/monitoring/prober/scd/test_operation_simple_heavy_traffic_concurrent.py @@ -258,7 +258,7 @@ def test_create_ops_concurrent(ids, scd_api, scd_session_async): op_id = req_map[0] op_resp_map[op_id] = {} op_resp_map[op_id]["status_code"] = resp[0][0] - op_resp_map[op_id]["content"] = resp[0][1] + op_resp_map[op_id]["content"] = resp[0][2] for op_id, resp in op_resp_map.items(): if resp["status_code"] != 201: try: @@ -342,7 +342,7 @@ def test_get_ops_by_ids_concurrent(ids, scd_api, scd_session_async): for op_id, resp in zip(map(ids, OP_TYPES), results): op_resp_map[op_id] = {} op_resp_map[op_id]["status_code"] = resp[0] - op_resp_map[op_id]["content"] = resp[1] + op_resp_map[op_id]["content"] = resp[2] for op_id, resp in op_resp_map.items(): assert resp["status_code"] == 200, resp["content"] @@ -381,7 +381,7 @@ def test_get_ops_by_search_concurrent(ids, scd_api, scd_session_async): for idx, resp in zip(range(len(OP_TYPES)), results): op_resp_map[idx] = {} op_resp_map[idx]["status_code"] = resp[0] - op_resp_map[idx]["content"] = resp[1] + op_resp_map[idx]["content"] = resp[2] for idx, resp in op_resp_map.items(): assert resp["status_code"] == 200, resp["content"] @@ -431,7 +431,7 @@ def test_mutate_ops_concurrent(ids, scd_api, scd_session, scd_session_async): op_id = req_map[0] op_resp_map[op_id] = {} op_resp_map[op_id]["status_code"] = resp[0][0] - op_resp_map[op_id]["content"] = resp[0][1] + op_resp_map[op_id]["content"] = resp[0][2] ovn_map.clear() @@ -486,7 +486,7 @@ def test_delete_op_concurrent(ids, scd_api, scd_session_async): for op_id, resp in zip(map(ids, OP_TYPES), results): op_resp_map[op_id] = {} op_resp_map[op_id]["status_code"] = resp[0] - op_resp_map[op_id]["content"] = resp[1] + op_resp_map[op_id]["content"] = resp[2] assert len(op_resp_map) == len(OP_TYPES) diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py index 2f9f3699c6..9c059217c2 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -1,12 +1,13 @@ from __future__ import annotations +import uuid from typing import Tuple, List, Dict, Optional + from urllib.parse import urlparse -from loguru import logger from implicitdict import ImplicitDict from monitoring.monitorlib import infrastructure, fetch from monitoring.monitorlib.fetch import QueryType -from monitoring.monitorlib.scd import SCOPE_SC +from monitoring.monitorlib.scd import SCOPE_SC, SCOPE_AA from monitoring.uss_qualifier.resources.resource import Resource from monitoring.uss_qualifier.resources.communications import AuthAdapterResource from uas_standards.astm.f3548.v21.api import ( @@ -16,6 +17,16 @@ QueryOperationalIntentReferenceResponse, OperationalIntent, GetOperationalIntentDetailsResponse, + PutOperationalIntentReferenceParameters, + EntityOVN, + OperationalIntentState, + ImplicitSubscriptionParameters, + UssBaseURL, + ChangeOperationalIntentReferenceResponse, + SubscriberToNotify, + SetUssAvailabilityStatusParameters, + UssAvailabilityState, + UssAvailabilityStatusResponse, ) @@ -123,6 +134,117 @@ def get_full_op_intent_without_validation( return result, query + def put_op_intent( + self, + extents: List[Volume4D], + key: List[EntityOVN], + state: OperationalIntentState, + base_url: UssBaseURL, + id: Optional[str] = None, + ovn: Optional[str] = None, + ) -> Tuple[ + Optional[OperationalIntentReference], + Optional[List[SubscriberToNotify]], + fetch.Query, + ]: + if id is None: + url = f"/dss/v1/operational_intent_references/{str(uuid.uuid4())}" + query_type = QueryType.F3548v21DSSCreateOperationalIntentReference + else: + url = f"/dss/v1/operational_intent_references/{id}/{ovn}" + query_type = QueryType.F3548v21DSSUpdateOperationalIntentReference + + req = PutOperationalIntentReferenceParameters( + extents=extents, + key=key, + state=state, + uss_base_url=base_url, + new_subscription=ImplicitSubscriptionParameters(uss_base_url=base_url), + ) + query = fetch.query_and_describe( + self.client, + "PUT", + url, + query_type, + self.participant_id, + scope=SCOPE_SC, + json=req, + ) + if query.status_code != 200 and query.status_code != 201: + return None, None, query + else: + result = ChangeOperationalIntentReferenceResponse( + ImplicitDict.parse( + query.response.json, ChangeOperationalIntentReferenceResponse + ) + ) + return result.operational_intent_reference, result.subscribers, query + + def delete_op_intent( + self, + id: str, + ovn: str, + ) -> Tuple[ + Optional[OperationalIntentReference], + Optional[List[SubscriberToNotify]], + fetch.Query, + ]: + query = fetch.query_and_describe( + self.client, + "DELETE", + f"/dss/v1/operational_intent_references/{id}/{ovn}", + QueryType.F3548v21DSSDeleteOperationalIntentReference, + self.participant_id, + scope=SCOPE_SC, + ) + if query.status_code != 200: + return None, None, query + else: + result = ChangeOperationalIntentReferenceResponse( + ImplicitDict.parse( + query.response.json, ChangeOperationalIntentReferenceResponse + ) + ) + return result.operational_intent_reference, result.subscribers, query + + def set_uss_availability( + self, + uss_id: str, + available: bool, + version: str = "", + ) -> Tuple[Optional[str], fetch.Query]: + """ + Returns: + A tuple composed of + 1) the new version of the USS availability, or None if the query failed; + 2) the query. + """ + if available: + availability = UssAvailabilityState.Normal + else: + availability = UssAvailabilityState.Down + + req = SetUssAvailabilityStatusParameters( + old_version=version, + availability=availability, + ) + query = fetch.query_and_describe( + self.client, + "PUT", + f"/dss/v1/uss_availability/{uss_id}", + QueryType.F3548v21DSSSetUssAvailability, + self.participant_id, + scope=SCOPE_AA, + json=req, + ) + if query.status_code != 200: + return None, query + else: + result = UssAvailabilityStatusResponse( + ImplicitDict.parse(query.response.json, UssAvailabilityStatusResponse) + ) + return result.version, query + def is_same_as(self, other: DSSInstance) -> bool: return ( self.participant_id == other.participant_id diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py index 741d57982f..f93923ee1d 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py @@ -2,6 +2,7 @@ import arrow +from monitoring.monitorlib import geo from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstanceResource @@ -17,6 +18,7 @@ class ISASubscriptionInteractions(GenericTestScenario): """Based on the test_subscription_isa_interactions.py from the legacy prober tool.""" ISA_TYPE = register_resource_type(370, "ISA") + SUB_TYPE = register_resource_type(373, "Subscription") def __init__( self, @@ -32,9 +34,10 @@ def __init__( self._isa_id = id_generator.id_factory.make_id( ISASubscriptionInteractions.ISA_TYPE ) - # sub id is isa_id with last character replaced with '1' - # (the generated isa_id ends with a few '0's) - self._sub_id = self._isa_id[:-1] + "1" + self._sub_id = id_generator.id_factory.make_id( + ISASubscriptionInteractions.SUB_TYPE + ) + self._isa_version: Optional[str] = None self._isa = isa.specification @@ -42,6 +45,25 @@ def __init__( self._isa_start_time = self._isa.shifted_time_start(now) self._isa_end_time = self._isa.shifted_time_end(now) self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._slight_overlap_area = geo.generate_slight_overlap_area(self._isa_area) + + self._isa_params = dict( + area_vertices=self._isa_area, + alt_lo=self._isa.altitude_min, + alt_hi=self._isa.altitude_max, + start_time=self._isa_start_time, + end_time=self._isa_end_time, + uss_base_url=self._isa.base_url, + isa_id=self._isa_id, + ) + self._sub_params = dict( + alt_lo=self._isa.altitude_min, + alt_hi=self._isa.altitude_max, + start_time=self._isa_start_time, + end_time=self._isa_end_time, + uss_base_url=self._isa.base_url, + sub_id=self._sub_id, + ) def run(self, context: ExecutionContext): self.begin_test_scenario(context) @@ -49,15 +71,32 @@ def run(self, context: ExecutionContext): self._setup_case() self.begin_test_case("ISA Subscription Interactions") - self.begin_test_step("ISA Subscription Interactions") - self._check_subscription_behaviors() + self.begin_test_step("New Subscription within ISA") + self._new_subscription_in_isa_step() + self.end_test_step() + self.begin_test_step("New subscription within ISA is mutated to ISA boundary") + self._mutate_subscription_towards_isa_boundary_step() self.end_test_step() + + # TODO extend with steps that: + # - create a subscription that barely touches the ISA + # - create a subscription outside of the ISA and then mutate to: + # - move the subscription into the ISA + # - move the subscription such that it barely touches the ISA + # - create a subscription within the ISA and then mutate to: + # - move the subscription entirely outside of the ISA + # - mutate the ISA so entirely outside of the subscription + # - mutate the ISA to the subscription boundary + # + # Consider doing the above with and without separate Subscription IDs to increase the chances + # of revealing implementation bugs + self.end_test_case() self.end_test_scenario() - def _check_subscription_behaviors(self): + def _new_subscription_in_isa_step(self): """ - Create an ISA. - Create a subscription, response should include the pre-existing ISA and have a notification_index of 0. @@ -71,14 +110,8 @@ def _check_subscription_behaviors(self): created_isa = self._dss_wrapper.put_isa_expect_response_code( check=check, expected_error_codes={200}, - area_vertices=self._isa_area, - alt_lo=self._isa.altitude_min, - alt_hi=self._isa.altitude_max, - start_time=self._isa_start_time, - end_time=self._isa_end_time, - uss_base_url=self._isa.base_url, - isa_id=self._isa_id, isa_version=None, + **self._isa_params, ) # Create a subscription @@ -88,13 +121,8 @@ def _check_subscription_behaviors(self): created_subscription = self._dss_wrapper.put_sub( check=check, area_vertices=self._isa_area, - alt_lo=self._isa.altitude_min, - alt_hi=self._isa.altitude_max, - start_time=self._isa_start_time, - end_time=self._isa_end_time, - uss_base_url=self._isa.base_url, - sub_id=self._sub_id, sub_version=None, + **self._sub_params, ) # Check the subscription @@ -134,17 +162,13 @@ def _check_subscription_behaviors(self): "Mutate the ISA", [self._dss.participant_id], ) as check: + isa_mutation_params = self._isa_params.copy() + isa_mutation_params["alt_hi"] -= 1 # reduce max altitude by one meter mutated_isa = self._dss_wrapper.put_isa_expect_response_code( check=check, expected_error_codes={200}, - area_vertices=self._isa_area, - alt_lo=self._isa.altitude_min, - alt_hi=self._isa.altitude_max - 1, # reduce max altitude by one meter - start_time=self._isa_start_time, - end_time=self._isa_end_time, - uss_base_url=self._isa.base_url, - isa_id=self._isa_id, isa_version=created_isa.dss_query.isa.version, + **isa_mutation_params, ) # Check that the subscription ID is returned in the response @@ -278,6 +302,225 @@ def _check_subscription_behaviors(self): sub_version=created_subscription.subscription.version, ) + def _mutate_subscription_towards_isa_boundary_step(self): + """ + - Create an ISA. + - Create a subscription with the ISA's area, response should include the pre-existing ISA + and have a notification_index of 0. + - Modify the subscription such that its area has a very small overlap with the ISA + - Modify the ISA, response should include the subscription with an incremented notification_index. + - Delete the ISA, response should include the subscription with an incremented notification_index. + - Delete the subscription. + """ + + # Create an ISA + with self.check("Create an ISA", [self._dss.participant_id]) as check: + created_isa = self._dss_wrapper.put_isa_expect_response_code( + check=check, + expected_error_codes={200}, + isa_version=None, + **self._isa_params, + ) + + # Create a subscription on the ISA boundary + with self.check( + "Create a subscription within the ISA footprint", [self._dss.participant_id] + ) as check: + created_subscription = self._dss_wrapper.put_sub( + check=check, + area_vertices=self._isa_area, + **self._sub_params, + ) + + # Mutate the subscription towards the ISA boundary + with self.check( + "Mutate the subscription towards the ISA boundary", + [self._dss.participant_id], + ) as check: + mutated_subscription = self._dss_wrapper.put_sub( + check=check, + area_vertices=self._slight_overlap_area, + sub_version=created_subscription.subscription.version, + **self._sub_params, + ) + + # Check the subscription + with self.check( + "Subscription for the ISA's area mentions the ISA", + [self._dss.participant_id], + ) as check: + if self._isa_id not in [isa.id for isa in mutated_subscription.isas]: + check.record_failed( + summary="Subscription response does not include the freshly created ISA", + severity=Severity.High, + participants=[self._dss.participant_id], + details=f"The subscription created for the area {self._isa_area} is expected to contain the ISA created for this same area. The returned subscription did not mention it.", + query_timestamps=[ + created_isa.dss_query.query.request.timestamp, + created_subscription.query.request.timestamp, + ], + ) + + with self.check( + "Mutated subscription has a notification_index of 0", + [self._dss.participant_id], + ) as check: + if created_subscription.subscription.notification_index != 0: + check.record_failed( + summary="Subscription notification_index is not 0", + severity=Severity.High, + participants=[self._dss.participant_id], + details=f"The subscription created for the area {self._isa_area} is expected to have a notification_index of 0. The returned subscription has a notification_index of {created_subscription.subscription.notification_index}.", + query_timestamps=[created_subscription.query.request.timestamp], + ) + + # Modify the ISA + with self.check( + "Mutate the ISA", + [self._dss.participant_id], + ) as check: + mutation_params = self._isa_params.copy() + mutation_params["alt_hi"] -= 1 # reduce max altitude by one meter + mutated_isa = self._dss_wrapper.put_isa_expect_response_code( + check=check, + expected_error_codes={200}, + isa_version=created_isa.dss_query.isa.version, + **mutation_params, + ) + + # Check that the subscription ID is returned in the response + with self.check( + "Response to the mutation of the ISA contains subscription ID", + [self._dss.participant_id], + ) as check: + + subs_to_mutated_isa = {} + for returned_subscriber in mutated_isa.dss_query.subscribers: + for sub_in_subscriber in returned_subscriber.raw.subscriptions: + subs_to_mutated_isa[ + sub_in_subscriber.subscription_id + ] = sub_in_subscriber + + if created_subscription.subscription.id not in subs_to_mutated_isa.keys(): + check.record_failed( + summary="ISA mutation response does not contain expected subscription ID", + severity=Severity.High, + participants=[self._dss.participant_id], + details="Mutating an ISA to which a subscription was made and then subsequently moved to the ISA's boundary," + " the DSS failed to return the subscription ID in the response.", + query_timestamps=[ + created_isa.dss_query.query.request.timestamp, + created_subscription.query.request.timestamp, + mutated_subscription.query.request.timestamp, + mutated_isa.dss_query.query.request.timestamp, + ], + ) + + # Check that the subscription index has been incremented by least by 1 + sub_to_mutated_isa = subs_to_mutated_isa.get( + created_subscription.subscription.id + ) + if sub_to_mutated_isa is not None: + with self.check( + "Subscription to an ISA has its notification index incremented after mutation", + [self._dss.participant_id], + ) as check: + if sub_to_mutated_isa.notification_index <= 0: + check.record_failed( + summary="Subscription notification_index has not been increased", + severity=Severity.High, + participants=[self._dss.participant_id], + details=f"The subscription created for the area {self._isa_area} is expected to have a notification_index of 1 or more. The returned subscription has a notification_index of {subs_to_mutated_isa[created_subscription.subscription.id].notification_index}.", + query_timestamps=[created_subscription.query.request.timestamp], + ) + + # Delete the ISA + with self.check( + "Delete the ISA", + [self._dss.participant_id], + ) as check: + deleted_isa = self._dss_wrapper.del_isa_expect_response_code( + main_check=check, + expected_error_codes={200}, + isa_id=mutated_isa.dss_query.isa.id, + isa_version=mutated_isa.dss_query.isa.version, + ) + + # Check response to deletion of ISA + with self.check( + "Response to the deletion of the ISA contains subscription ID", + [self._dss.participant_id], + ) as check: + + subs_to_deleted_isa = {} + for returned_subscriber in deleted_isa.dss_query.subscribers: + for sub_in_subscriber in returned_subscriber.raw.subscriptions: + subs_to_deleted_isa[ + sub_in_subscriber.subscription_id + ] = sub_in_subscriber + + if created_subscription.subscription.id not in subs_to_deleted_isa: + check.record_failed( + summary="ISA deletion response does not contain expected subscription ID", + severity=Severity.High, + participants=[self._dss.participant_id], + details="Deleting an ISA to which a subscription was made, the DSS failed to return the subscription ID in the response.", + query_timestamps=[ + created_isa.dss_query.query.request.timestamp, + created_subscription.query.request.timestamp, + deleted_isa.dss_query.query.request.timestamp, + ], + ) + + for subscriber_url, notification in deleted_isa.notifications.items(): + # For checking the notifications, we ignore the request we made for the subscription that we created. + if self._isa.base_url not in subscriber_url: + pid = ( + notification.query.participant_id + if "participant_id" in notification.query + else None + ) + with self.check("Notified subscriber", [pid] if pid else []) as check: + if not notification.success: + check.record_failed( + "Could not notify ISA subscriber", + Severity.Medium, + f"Attempting to notify subscriber for ISA {self._isa_id} at {subscriber_url} resulted in {notification.status_code}", + query_timestamps=[notification.query.request.timestamp], + ) + + subs_after_deletion = subs_to_deleted_isa.get( + created_subscription.subscription.id + ) + if subs_after_deletion is not None: + with self.check( + "Subscription to an ISA has its notification index incremented after deletion", + [self._dss.participant_id], + ) as check: + if ( + subs_after_deletion.notification_index + <= sub_to_mutated_isa.notification_index + ): + check.record_failed( + summary="Subscription notification_index has not been incremented", + severity=Severity.High, + participants=[self._dss.participant_id], + details=f"The subscription created for the area {self._isa_area} is expected to have its notification increased after the subscription was deleted." + f"The returned subscription has a notification_index of {subs_after_deletion.notification_index}, whilte the previous notification_index for that subscription was {sub_to_mutated_isa.notification_index}", + query_timestamps=[created_subscription.query.request.timestamp], + ) + + # Delete the subscription + with self.check( + "Subscription can be deleted", + [self._dss.participant_id], + ) as check: + self._dss_wrapper.del_sub( + check=check, + sub_id=self._sub_id, + sub_version=mutated_subscription.subscription.version, + ) + def _setup_case(self): self.begin_test_case("Setup") @@ -305,6 +548,9 @@ def _delete_isa_if_exists(self): def _clean_any_sub(self): self._dss_wrapper.cleanup_subs_in_area(self._isa_area) + # Explicitly clean up in the separate area in case the DSS does + # not return that subscription when searching in the ISA's footpring + self._dss_wrapper.cleanup_subs_in_area(self._slight_overlap_area) def cleanup(self): self.begin_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py index 036876ed93..54e20a6655 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py @@ -1,5 +1,7 @@ from typing import Optional +from s2sphere import LatLng + from monitoring.monitorlib.fetch import rid as fetch from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.mutate import rid as mutate diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py b/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py index f1f1fb2de5..bbc277b0ca 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py @@ -303,6 +303,9 @@ def put_isa_expect_response_code( participant_id=self._dss.participant_id, ) + for notification_query in mutated_isa.notifications.values(): + self._scenario.record_query(notification_query.query) + self.handle_query_result( check=check, q=mutated_isa.dss_query, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_subscription_interactions.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_subscription_interactions.md index 6b66615868..9fcbb82edf 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_subscription_interactions.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_subscription_interactions.md @@ -51,7 +51,7 @@ When a pre-existing ISA needs to be deleted to ensure a clean workspace, any sub This test case will do the following, using the DSS being tested: 1. Create an ISA with the configured footprint, -2. Create a subscription for the ISA's area, and expect: +2. Do several variants of creating and possibly mutating a subscription, either in or close to the ISA's area, and expect: - to find the created ISA mentioned in the reply - the notification index of the subscription to be 0 3. Modify the ISA, and expect: @@ -62,7 +62,10 @@ This test case will do the following, using the DSS being tested: - the notification index of the subscription to be greater than it was after the mutation 5. Delete the subscription. -### ISA Subscription Interactions test step +### New Subscription within ISA test step + +This test step checks for interactions between ISAs and a subscription that is created within the ISA, then +subsequently mutated to only barely intersect with the ISA. #### Create an ISA check @@ -122,6 +125,78 @@ Failure to do so means that the DSS is not properly implementing **[astm.f3411.v Notifications to any subscriber to the created ISA need to be successful. If a notification cannot be delivered, then the **[astm.f3411.v19.NET0730](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the POST ISAs endpoint isn't met. +### New subscription within ISA is mutated to ISA boundary test step + +This test step checks for interactions between ISAs and a subscription that is created within the ISA and the mutated +to only barely overlap with the ISA. + +#### Create an ISA check + +If the ISA cannot be created, the PUT DSS endpoint in **[astm.f3411.v19.DSS0030,a](../../../../../requirements/astm/f3411/v19.md)** is likely not implemented correctly. + +#### Create a subscription within the ISA footprint check + +The DSS should allow the creation of a subscription within the ISA footprint, otherwise it is in violation of **[astm.f3411.v19.DSS0030,c](../../../../../requirements/astm/f3411/v19.md)** + +#### Mutate the subscription towards the ISA boundary check + +The DSS should allow a valid mutation of a subscription's area, otherwise it is in violation of **[astm.f3411.v19.DSS0030,c](../../../../../requirements/astm/f3411/v19.md)** + +#### Subscription for the ISA's area mentions the ISA check + +A subscription that is created for a volume that intersects with the previously created ISA should mention +the previously created ISA. If not, the serving DSS is in violation of **[astm.f3411.v19.DSS0030,c](../../../../../requirements/astm/f3411/v19.md)**. + +#### Mutated subscription has a notification_index of 0 check + +A newly created subscription is expected to have a notification index of 0, otherwise the DSS implementation under +test does not comply with **[astm.f3411.v19.DSS0030,c](../../../../../requirements/astm/f3411/v19.md)** + +#### Mutate the ISA check + +If the ISA cannot be mutated, **[astm.f3411.v19.DSS0030,a](../../../../../requirements/astm/f3411/v19.md)** is likely not implemented correctly. + +#### Response to the mutation of the ISA contains subscription ID check + +When an ISA is mutated, the DSS must return the identifiers for any subscription that was made to the ISA, +or be in violation of **[astm.f3411.v19.DSS0030,a](../../../../../requirements/astm/f3411/v19.md)**. + +#### Subscription to an ISA has its notification index incremented after mutation check + +When an ISA is mutated, the DSS must increment the notification index of any subscription to that ISA, +and return the up-to-date subscription in the response to the query mutating the ISA. + +Failure to do so means that the DSS is not properly implementing **[astm.f3411.v19.DSS0030,a](../../../../../requirements/astm/f3411/v19.md)**. + +#### Subscription that only barely overlaps the ISA contains the ISA check + +A subscription that is created for a volume that only barely overlaps with the previously created ISA should still +contain the ISA in the reply from the server, otherwise the DSS does not comply with **[astm.f3411.v19.DSS0030,c](../../../../../requirements/astm/f3411/v19.md)** + +#### Delete the ISA check + +If that ISA cannot be deleted, the **[astm.f3411.v19.DSS0030,d](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the ISA deletion endpoint might not be met. + +#### Response to the deletion of the ISA contains subscription ID check + +When an ISA is deleted, the DSS must return the identifiers for any subscription that was made to the ISA, +or be in violation of **[astm.f3411.v19.DSS0030,b](../../../../../requirements/astm/f3411/v19.md)**. + +#### Subscription to an ISA has its notification index incremented after deletion check + +When an ISA is deleted, the DSS must increment the notification index of any subscription to that ISA, +and return the up-to-date subscription in the response to the query deleting the ISA. + +Failure to do so means that the DSS is not properly implementing **[astm.f3411.v19.DSS0030,a](../../../../../requirements/astm/f3411/v19.md)**. + +#### Subscription can be deleted check + +**[astm.f3411.v19.DSS0030,d](../../../../../requirements/astm/f3411/v19.md)** requires the implementation of the DSS endpoint to allow callers to delete subscriptions they created. + +#### Notified subscriber check + +Notifications to any subscriber to the created ISA need to be successful. If a notification cannot be delivered, then the **[astm.f3411.v19.NET0730](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the POST ISAs endpoint isn't met. + ## Cleanup The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely while diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_subscription_interactions.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_subscription_interactions.md index 526545c24a..8e78991083 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_subscription_interactions.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_subscription_interactions.md @@ -51,7 +51,7 @@ When a pre-existing ISA needs to be deleted to ensure a clean workspace, any sub This test case will do the following, using the DSS being tested: 1. Create an ISA with the configured footprint, -2. Create a subscription for the ISA's area, and expect: +2. Do several variants of creating and possibly mutating a subscription, either in or close to the ISA's area, and expect: - to find the created ISA mentioned in the reply - the notification index of the subscription to be 0 3. Modify the ISA, and expect: @@ -62,7 +62,10 @@ This test case will do the following, using the DSS being tested: - the notification index of the subscription to be greater than it was after the mutation 5. Delete the subscription. -### ISA Subscription Interactions test step +### New Subscription within ISA test step + +This test step checks for interactions between ISAs and a subscription that is created within the ISA, then +subsequently mutated to only barely intersect with the ISA. #### Create an ISA check @@ -122,6 +125,78 @@ Failure to do so means that the DSS is not properly implementing **[astm.f3411.v Notifications to any subscriber to the created ISA need to be successful. If a notification cannot be delivered, then the **[astm.f3411.v22a.NET0730](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the POST ISAs endpoint isn't met. +### New subscription within ISA is mutated to ISA boundary test step + +This test step checks for interactions between ISAs and a subscription that is created within the ISA and the mutated +to only barely overlap with the ISA. + +#### Create an ISA check + +If the ISA cannot be created, the PUT DSS endpoint in **[astm.f3411.v22a.DSS0030,a](../../../../../requirements/astm/f3411/v22a.md)** is likely not implemented correctly. + +#### Create a subscription within the ISA footprint check + +The DSS should allow the creation of a subscription within the ISA footprint, otherwise it is in violation of **[astm.f3411.v22a.DSS0030,c](../../../../../requirements/astm/f3411/v22a.md)** + +#### Mutate the subscription towards the ISA boundary check + +The DSS should allow a valid mutation of a subscription's area, otherwise it is in violation of **[astm.f3411.v22a.DSS0030,c](../../../../../requirements/astm/f3411/v22a.md)** + +#### Subscription for the ISA's area mentions the ISA check + +A subscription that is created for a volume that intersects with the previously created ISA should mention +the previously created ISA. If not, the serving DSS is in violation of **[astm.f3411.v22a.DSS0030,c](../../../../../requirements/astm/f3411/v22a.md)**. + +#### Mutated subscription has a notification_index of 0 check + +A newly created subscription is expected to have a notification index of 0, otherwise the DSS implementation under +test does not comply with **[astm.f3411.v22a.DSS0030,c](../../../../../requirements/astm/f3411/v22a.md)** + +#### Mutate the ISA check + +If the ISA cannot be mutated, **[astm.f3411.v22a.DSS0030,a](../../../../../requirements/astm/f3411/v22a.md)** is likely not implemented correctly. + +#### Response to the mutation of the ISA contains subscription ID check + +When an ISA is mutated, the DSS must return the identifiers for any subscription that was made to the ISA, +or be in violation of **[astm.f3411.v22a.DSS0030,a](../../../../../requirements/astm/f3411/v22a.md)**. + +#### Subscription to an ISA has its notification index incremented after mutation check + +When an ISA is mutated, the DSS must increment the notification index of any subscription to that ISA, +and return the up-to-date subscription in the response to the query mutating the ISA. + +Failure to do so means that the DSS is not properly implementing **[astm.f3411.v22a.DSS0030,a](../../../../../requirements/astm/f3411/v22a.md)**. + +#### Subscription that only barely overlaps the ISA contains the ISA check + +A subscription that is created for a volume that only barely overlaps with the previously created ISA should still +contain the ISA in the reply from the server, otherwise the DSS does not comply with **[astm.f3411.v22a.DSS0030,c](../../../../../requirements/astm/f3411/v22a.md)** + +#### Delete the ISA check + +If that ISA cannot be deleted, the **[astm.f3411.v22a.DSS0030,d](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the ISA deletion endpoint might not be met. + +#### Response to the deletion of the ISA contains subscription ID check + +When an ISA is deleted, the DSS must return the identifiers for any subscription that was made to the ISA, +or be in violation of **[astm.f3411.v22a.DSS0030,b](../../../../../requirements/astm/f3411/v22a.md)**. + +#### Subscription to an ISA has its notification index incremented after deletion check + +When an ISA is deleted, the DSS must increment the notification index of any subscription to that ISA, +and return the up-to-date subscription in the response to the query deleting the ISA. + +Failure to do so means that the DSS is not properly implementing **[astm.f3411.v22a.DSS0030,a](../../../../../requirements/astm/f3411/v22a.md)**. + +#### Subscription can be deleted check + +**[astm.f3411.v22a.DSS0030,d](../../../../../requirements/astm/f3411/v22a.md)** requires the implementation of the DSS endpoint to allow callers to delete subscriptions they created. + +#### Notified subscriber check + +Notifications to any subscriber to the created ISA need to be successful. If a notification cannot be delivered, then the **[astm.f3411.v22a.NET0730](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the POST ISAs endpoint isn't met. + ## Cleanup The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely while diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py index fee5010cb1..e87ac2555b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py @@ -8,3 +8,4 @@ from .dss_interoperability import DSSInteroperability from .aggregate_checks import AggregateChecks from .prep_planners import PrepareFlightPlanners +from .off_nominal_planning.down_uss import DownUSS diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md new file mode 100644 index 0000000000..24556a788c --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md @@ -0,0 +1,125 @@ +# Off-Nominal planning: down USS test scenario + +## Description +This test aims to test the strategic coordination requirements that relate to the down USS mechanism: +- **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)** +- **[astm.f3548.v21.SCD0010](../../../../requirements/astm/f3548/v21.md)** + +It involves a single tested USS. The USS qualifier acts as a virtual USS that may have its availability set to down. + +## Resources +### flight_intents +FlightIntentsResource that provides the following flight intents: + + + + + + + + + + + + + + + + + + + + + + + + + + +
Flight intent IDFlight namePriorityStateMust conflict withMust not conflict with
flight_1_planned_vol_AFlight 1AnyAcceptedFlight 2Flight 2m
flight_2_planned_vol_AFlight 2Higher than Flight 1*AcceptedFlight 1N/A
+ + +### tested_uss +FlightPlannerResource that is under test and will manage flight 1. + +### dss +DSSInstanceResource that provides access to a DSS instance where: +- flight creation/sharing can be verified, +- the USS qualifier acting as a virtual USS can create operational intents, and +- the USS qualifier can act as an availability arbitrator. + +## Setup test case +### Resolve USS ID of virtual USS test step +Make a dummy request to the DSS in order to resolve the USS ID of the virtual USS. + +#### Successful dummy query check + +### [Restore virtual USS availability test step](../set_uss_available.md) + +### Clear operational intents created by virtual USS test step +Delete any leftover operational intents created at DSS by virtual USS. + +#### Successful operational intents cleanup check +If the search for own operational intents or their deletion fail, this check fails per **[astm.f3548.v21.DSS0005](../../../../requirements/astm/f3548/v21.md)**. + +## Plan flight in conflict with planned flight managed by down USS test case +This test case aims at testing requirement **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)**. + +### Virtual USS plans high-priority flight 2 test step +The USS qualifier, acting as a virtual USS, creates an operational intent at the DSS with a high priority and a non-working base URL. +The objective is to make the later request by the tested USS to retrieve operational intent details to fail. + +#### Operational intent successfully created check +If the creation of the operational intent reference at the DSS fails, this check fails per **[astm.f3548.v21.DSS0005](../../../../requirements/astm/f3548/v21.md)**. + +### [Declare virtual USS as down at DSS test step](../set_uss_down.md) + +### Tested USS attempts to plan low-priority flight 1 test step +The low-priority flight 1 of the tested USS conflicts with high-priority flight 2 of the virtual USS. +However, since: +- the virtual USS is declared as down at the DSS, +- it does not respond for operational intent details, and +- the operational intent for flight 2 is in 'Planned' state, +The tested USS should evaluate the operational intent of flight 2 as having the lowest bound priority status, i.e. a priority strictly lower than the lowest priority allowed by the local regulation. + +As such, the tested USS may either: +- Successfully plan flight 1 over the higher-priority flight 2, or +- Decide to be more conservative and reject the planning of flight 1. + +#### Successful planning check +All flight intent data provided is correct and the USS should have either successfully planned the flight per **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)**, +or rejected properly the planning if it decided to be more conservative with such conflicts. +If the USS indicates that the injection attempt failed, this check will fail. + +Do take note that if the USS rejects the planning, this check will not fail, but the following *Rejected planning check* +will. Refer to this check for more information. + +#### Rejected planning check +All flight intent data provided is correct and the USS should have either successfully planned the flight or rejected +properly the planning if it decided to be more conservative with such conflicts. +If the USS rejects the planning, this check will fail with a low severity per **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)**. +This won't actually fail the test but will serve as a warning. + +#### Failure check +All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS +to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per +**[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../requirements/interuss/automated_testing/flight_planning.md)**. + +### [Validate low-priority flight 1 status test step](../validate_shared_operational_intent.md) +This step validates that the response of the USS is consistent with the flight shared, i.e. either it was properly +planned, or the USS rejected the planning. + +If the planning was accepted, flight 1 should have been shared. +If the planning was rejected, flight 1 should not have been shared, thus should not exist. + +## Cleanup +### Availability of virtual USS restored check +**[astm.f3548.v21.DSS0100](../../../../requirements/astm/f3548/v21.md)** + +### Successful flight deletion check +Delete flights injected at USS through the flight planning interface. +**[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../requirements/interuss/automated_testing/flight_planning.md)** + +### Successful operational intents cleanup check +Delete operational intents created at DSS by virtual USS. +If the search for own operational intents or their deletion fail, this check fails per **[astm.f3548.v21.DSS0005](../../../../requirements/astm/f3548/v21.md)**. 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 new file mode 100644 index 0000000000..2b766306bc --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py @@ -0,0 +1,299 @@ +from typing import Optional, List + +import arrow + +from monitoring.monitorlib.geotemporal import Volume4DCollection +from monitoring.uss_qualifier.common_data_definitions import Severity +from uas_standards.astm.f3548.v21.api import OperationalIntentState +from uas_standards.interuss.automated_testing.scd.v1.api import ( + InjectFlightResponseResult, +) + +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 ( + FlightIntentsResource, +) +from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( + FlightIntent, +) +from monitoring.uss_qualifier.resources.flight_planning.flight_planner import ( + FlightPlanner, +) +from monitoring.uss_qualifier.resources.flight_planning.flight_planners import ( + FlightPlannerResource, +) +from monitoring.uss_qualifier.scenarios.astm.utm.test_steps import ( + OpIntentValidator, + set_uss_available, + set_uss_down, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.scenarios.flight_planning.test_steps import ( + cleanup_flights, + submit_flight_intent, +) +from monitoring.uss_qualifier.suites.suite import ExecutionContext + + +class DownUSS(TestScenario): + flight_1_id: Optional[str] = None + flight_1_planned_vol_A: FlightIntent + + flight_2_planned_vol_A: FlightIntent + + uss_qualifier_sub: str + + tested_uss: FlightPlanner + dss: DSSInstance + + def __init__( + self, + flight_intents: FlightIntentsResource, + tested_uss: FlightPlannerResource, + dss: DSSInstanceResource, + ): + super().__init__() + self.tested_uss = tested_uss.flight_planner + self.dss = dss.dss + + _flight_intents = { + k: FlightIntent.from_flight_info_template(v) + for k, v in flight_intents.get_flight_intents().items() + } + + extents = [] + for intent in _flight_intents.values(): + extents.extend(intent.request.operational_intent.volumes) + extents.extend(intent.request.operational_intent.off_nominal_volumes) + self._intents_extent = Volume4DCollection.from_interuss_scd_api( + extents + ).bounding_volume.to_f3548v21() + + try: + (self.flight_1_planned_vol_A, self.flight_2_planned_vol_A,) = ( + _flight_intents["flight_1_planned_vol_A"], + _flight_intents["flight_2_planned_vol_A"], + ) + + now = arrow.utcnow().datetime + for intent_name, intent in _flight_intents.items(): + if ( + intent.request.operational_intent.state + == OperationalIntentState.Activated + ): + assert Volume4DCollection.from_interuss_scd_api( + intent.request.operational_intent.volumes + + intent.request.operational_intent.off_nominal_volumes + ).has_active_volume( + now + ), f"at least one volume of activated intent {intent_name} must be active now (now is {now})" + + assert ( + self.flight_1_planned_vol_A.request.operational_intent.state + == OperationalIntentState.Accepted + ), "flight_1_planned_vol_A must have state Accepted" + assert ( + self.flight_2_planned_vol_A.request.operational_intent.state + == OperationalIntentState.Accepted + ), "flight_2_planned_vol_A must have state Accepted" + + # TODO: check that flight data is the same across the different versions of the flight + + assert ( + self.flight_2_planned_vol_A.request.operational_intent.priority + > self.flight_1_planned_vol_A.request.operational_intent.priority + ), "flight_2 must have higher priority than flight_1" + assert Volume4DCollection.from_interuss_scd_api( + self.flight_1_planned_vol_A.request.operational_intent.volumes + ).intersects_vol4s( + Volume4DCollection.from_interuss_scd_api( + self.flight_2_planned_vol_A.request.operational_intent.volumes + ) + ), "flight_1_planned_vol_A and flight_2_planned_vol_A must intersect" + + except KeyError as e: + raise ValueError( + f"`{self.me()}` TestScenario requirements for flight_intents not met: missing flight intent {e}" + ) + except AssertionError as e: + raise ValueError( + f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" + ) + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + + self.record_note( + "Tested USS", + f"{self.tested_uss.config.participant_id}", + ) + + self.begin_test_case("Setup") + self._setup() + self.end_test_case() + + self.begin_test_case( + "Plan flight in conflict with planned flight managed by down USS" + ) + self._plan_flight_conflict_planned() + self.end_test_case() + + self.end_test_scenario() + + 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: + check.record_failed( + summary="Failed to query DSS", + severity=Severity.High, + details=f"DSS responded code {dummy_query.status_code}; error message: {dummy_query.error_message}", + query_timestamps=[dummy_query.request.timestamp], + ) + self.uss_qualifier_sub = self.dss.client.auth_adapter.get_sub() + self.record_note( + "USS ID of virtual USS", + f"{self.uss_qualifier_sub}", + ) + self.end_test_step() + + set_uss_available( + self, "Restore virtual USS availability", self.dss, self.uss_qualifier_sub + ) + + self.begin_test_step("Clear operational intents created by virtual USS") + self._clear_op_intents() + self.end_test_step() + + def _plan_flight_conflict_planned(self): + + # Virtual USS plans high-priority flight 2 test step + self.begin_test_step("Virtual USS plans high-priority flight 2") + oi_ref, _, query = self.dss.put_op_intent( + Volume4DCollection.from_interuss_scd_api( + self.flight_2_planned_vol_A.request.operational_intent.volumes + ).to_f3548v21(), + [], # we assume there is no other operational intent in that area + OperationalIntentState.Accepted, + "https://fake.uss/down", + ) + self.record_query(query) + with self.check( + "Operational intent successfully created", [self.dss.participant_id] + ) as check: + if oi_ref is None: + check.record_failed( + "Operational intent not successfully created", + Severity.High, + f"DSS responded code {query.status_code}; error message: {query.error_message}", + query_timestamps=[query.request.timestamp], + ) + self.end_test_step() + + # Declare virtual USS as down at DSS test step + set_uss_down( + self, "Declare virtual USS as down at DSS", self.dss, self.uss_qualifier_sub + ) + + # Tested USS attempts to plan low-priority flight 1 test step + with OpIntentValidator( + self, + self.tested_uss, + self.dss, + "Validate low-priority flight 1 status", + self._intents_extent, + ) as validator: + expected_results = { + InjectFlightResponseResult.Planned, + # the following two results are considered expected in order to fail another check as low severity + InjectFlightResponseResult.Rejected, + InjectFlightResponseResult.ConflictWithFlight, + } + failed_checks = { + InjectFlightResponseResult.Failed: "Failure", + InjectFlightResponseResult.Rejected: ( + "Rejected planning", + Severity.Low, + ), + InjectFlightResponseResult.ConflictWithFlight: ( + "Rejected planning", + Severity.Low, + ), + } + + resp, flight_id = submit_flight_intent( + self, + "Tested USS attempts to plan low-priority flight 1", + "Successful planning", + expected_results, + failed_checks, + self.tested_uss, + self.flight_1_planned_vol_A.request, + ) + + if resp.result == InjectFlightResponseResult.Planned: + validator.expect_shared(self.flight_1_planned_vol_A.request) + elif ( + resp.result == InjectFlightResponseResult.Rejected + or resp.result == InjectFlightResponseResult.ConflictWithFlight + ): + validator.expect_not_shared() + + 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: + check.record_failed( + summary=f"Failed to query operational intents from DSS in {self._intents_extent} for cleanup", + severity=Severity.High, + details=f"DSS responded code {find_query.status_code}; error message: {find_query.error_message}", + query_timestamps=[find_query.request.timestamp], + ) + + for oi_ref in oi_refs: + if ( + oi_ref.ovn is not None + ): # if the OVN is specified, this op intent belongs to our virtual USS + del_oi, _, del_query = self.dss.delete_op_intent( + oi_ref.id, oi_ref.ovn + ) + self.record_query(del_query) + + if del_oi is None: + check.record_failed( + summary=f"Failed to delete op intent {oi_ref.id} from DSS", + severity=Severity.Medium, + details=f"DSS responded code {del_query.status_code}; error message: {del_query.error_message}", + query_timestamps=[del_query.request.timestamp], + ) + + def cleanup(self): + self.begin_cleanup() + + with self.check( + "Availability of virtual USS restored", [self.dss.participant_id] + ) as check: + availability_version, avail_query = self.dss.set_uss_availability( + self.uss_qualifier_sub, + True, + ) + self.record_query(avail_query) + if availability_version is None: + check.record_failed( + summary=f"Availability of USS {self.uss_qualifier_sub} could not be set to available", + severity=Severity.High, + details=f"DSS responded code {avail_query.status_code}; error message: {avail_query.error_message}", + query_timestamps=[avail_query.request.timestamp], + ) + + cleanup_flights(self, [self.tested_uss]) + self._clear_op_intents() + + self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_available.md b/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_available.md new file mode 100644 index 0000000000..67870dc7f5 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_available.md @@ -0,0 +1,8 @@ +# Set USS availability to 'Available' test step + +This step sets the USS availability to 'Available' at the DSS. + +See `set_uss_available` in [test_steps.py](test_steps.py). + +## USS availability successfully set to 'Available' check +**[astm.f3548.v21.DSS0100](../../../requirements/astm/f3548/v21.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_down.md b/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_down.md new file mode 100644 index 0000000000..e470279f81 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_down.md @@ -0,0 +1,8 @@ +# Set USS availability to 'Down' test step + +This step sets the USS availability to 'Down' at the DSS. + +See `set_uss_down` in [test_steps.py](test_steps.py). + +## USS availability successfully set to 'Down' check +**[astm.f3548.v21.DSS0100](../../../requirements/astm/f3548/v21.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py index ca2baadc3e..3b122a0062 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py @@ -432,3 +432,69 @@ def _operational_intent_shared_check( oi_ref = self._new_oi_ref return oi_ref + + +def set_uss_available( + scenario: TestScenarioType, + test_step: str, + dss: DSSInstance, + uss_sub: str, +) -> str: + """Set the USS availability to 'Available'. + + This function implements the test step described in set_uss_available.md. + + Returns: + The new version of the USS availability. + """ + scenario.begin_test_step(test_step) + availability_version, avail_query = dss.set_uss_availability( + uss_sub, + True, + ) + scenario.record_query(avail_query) + with scenario.check( + "USS availability successfully set to 'Available'", [dss.participant_id] + ) as check: + if availability_version is None: + check.record_failed( + summary=f"Availability of USS {uss_sub} could not be set to available", + severity=Severity.High, + details=f"DSS responded code {avail_query.status_code}; error message: {avail_query.error_message}", + query_timestamps=[avail_query.request.timestamp], + ) + scenario.end_test_step() + return availability_version + + +def set_uss_down( + scenario: TestScenarioType, + test_step: str, + dss: DSSInstance, + uss_sub: str, +) -> str: + """Set the USS availability to 'Down'. + + This function implements the test step described in set_uss_down.md. + + Returns: + The new version of the USS availability. + """ + scenario.begin_test_step(test_step) + availability_version, avail_query = dss.set_uss_availability( + uss_sub, + False, + ) + scenario.record_query(avail_query) + with scenario.check( + "USS availability successfully set to 'Down'", [dss.participant_id] + ) as check: + if availability_version is None: + check.record_failed( + summary=f"Availability of USS {uss_sub} could not be set to down", + severity=Severity.High, + details=f"DSS responded code {avail_query.status_code}; error message: {avail_query.error_message}", + query_timestamps=[avail_query.request.timestamp], + ) + scenario.end_test_step() + return availability_version diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py b/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py index a90d4264ad..f02a2db5d8 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py @@ -441,7 +441,7 @@ def submit_flight( specific_failed_check.record_failed( summary=f"Flight unexpectedly {resp.activity_result}", severity=check_severity, - details=f'{flight_planner.participant_id} indicated {resp.activity_result} rather than the expected {" or ".join([activity_res for activity_res,_ in expected_results])}{notes_suffix}', + details=f'{flight_planner.participant_id} indicated {resp.activity_result} rather than the expected {" or ".join(r[0] for r in expected_results)}{notes_suffix}', query_timestamps=[query.request.timestamp], ) @@ -450,9 +450,9 @@ def submit_flight( return resp, flight_id else: check.record_failed( - summary=f"Flight unexpectedly {resp.activity_result}", + summary=f"Flight planning activity outcome was not expected", severity=Severity.High, - details=f'{flight_planner.participant_id} indicated {resp.activity_result} rather than the expected {" or ".join([activity_res for activity_res,_ in expected_results])}{notes_suffix}', + details=f'{flight_planner.participant_id} indicated {resp.activity_result} with flight plan status {resp.flight_plan_status} rather than the expected {" or ".join(f"({expected_results[0]}, {expected_results[1]})")}{notes_suffix}', query_timestamps=[query.request.timestamp], ) @@ -549,9 +549,9 @@ def delete_flight( return resp else: check.record_failed( - summary=f"Flight deletion attempt unexpectedly {(resp.activity_result,resp.flight_plan_status)}", + summary=f"Flight deletion attempt unexpectedly {resp.activity_result} with flight plan status {resp.flight_plan_status}", severity=Severity.High, - details=f"{flight_planner.participant_id} indicated {(resp.activity_result,resp.flight_plan_status)} rather than the expected {PlanningActivityResult.Completed,FlightPlanStatus.Closed}{notes_suffix}", + details=f"{flight_planner.participant_id} indicated {resp.activity_result} with flight plan status {resp.flight_plan_status} rather than the expected Completed with flight plan status Closed{notes_suffix}", query_timestamps=[query.request.timestamp], ) @@ -599,7 +599,7 @@ def cleanup_flights_fp_client( else: check.record_failed( summary="Failed to delete flight", - details=f"USS indicated: {resp.notes}" + details=f"USS indicated {resp.activity_result} with flight plan status {resp.flight_plan_status} rather than the expected Completed with flight plan status Closed. Its notes were: {resp.notes}" if "notes" in resp else "See query", severity=Severity.Medium, diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index d6a392f459..4c49a431c3 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -15,7 +15,9 @@ 1. Scenario: [Nominal planning: not permitted conflict with equal priority](../../../scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.md) ([`scenarios.astm.utm.ConflictEqualPriorityNotPermitted`](../../../scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py)) 6. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Data Validation of GET operational intents by USS](../../../scenarios/astm/utm/data_exchange_validation/get_op_data_validation.md) ([`scenarios.astm.utm.data_exchange_validation.GetOpResponseDataValidationByUSS`](../../../scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py)) -7. Scenario: [ASTM F3548 UTM aggregate checks](../../../scenarios/astm/utm/aggregate_checks.md) ([`scenarios.astm.utm.AggregateChecks`](../../../scenarios/astm/utm/aggregate_checks.py)) +7. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) + 1. Scenario: [Off-Nominal planning: down USS](../../../scenarios/astm/utm/off_nominal_planning/down_uss.md) ([`scenarios.astm.utm.DownUSS`](../../../scenarios/astm/utm/off_nominal_planning/down_uss.py)) +8. Scenario: [ASTM F3548 UTM aggregate checks](../../../scenarios/astm/utm/aggregate_checks.md) ([`scenarios.astm.utm.AggregateChecks`](../../../scenarios/astm/utm/aggregate_checks.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -27,10 +29,15 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents + + + DSS0100 + Implemented + Off-Nominal planning: down USS DSS0300 @@ -50,17 +57,17 @@ OPIN0015 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0020 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0025 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0030 @@ -72,6 +79,11 @@ Implemented Validation of operational intents + + SCD0005 + Implemented + Off-Nominal planning: down USS + SCD0015 Implemented @@ -125,12 +137,12 @@ USS0005 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents USS0105 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents interuss
.automated_testing
.flight_planning
@@ -141,17 +153,17 @@ DeleteFlightSuccess Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ExpectedBehavior Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents FlightCoveredByOperationalIntent Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ImplementAPI diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml index 8ff3d6a2cc..77e81f5ad6 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml @@ -125,6 +125,27 @@ actions: roles: - uss1 on_failure: Continue +- action_generator: + generator_type: action_generators.flight_planning.FlightPlannerCombinations + resources: + flight_planners: flight_planners + nominal_planning_selector: nominal_planning_selector? + conflicting_flights: conflicting_flights + dss: dss + specification: + action_to_repeat: + test_scenario: + scenario_type: scenarios.astm.utm.DownUSS + resources: + flight_intents: conflicting_flights + tested_uss: uss1 + dss: dss + on_failure: Continue + combination_selector_source: nominal_planning_selector + flight_planners_source: flight_planners + roles: + - uss1 + on_failure: Continue - test_scenario: scenario_type: scenarios.astm.utm.AggregateChecks resources: diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index 0db857d77a..475cae5788 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md @@ -18,10 +18,15 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents + + + DSS0100 + Implemented + Off-Nominal planning: down USS DSS0300 @@ -41,17 +46,17 @@ OPIN0015 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0020 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0025 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0030 @@ -63,6 +68,11 @@ Implemented Validation of operational intents + + SCD0005 + Implemented + Off-Nominal planning: down USS + SCD0015 Implemented @@ -116,12 +126,12 @@ USS0005 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents USS0105 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents interuss
.automated_testing
.flight_planning
@@ -132,17 +142,17 @@ DeleteFlightSuccess Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ExpectedBehavior Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents FlightCoveredByOperationalIntent Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ImplementAPI diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index 927a06c149..1a677d1cd7 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.md +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.md @@ -19,10 +19,15 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents + + + DSS0100 + Implemented + Off-Nominal planning: down USS DSS0300 @@ -42,17 +47,17 @@ OPIN0015 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0020 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0025 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0030 @@ -64,6 +69,11 @@ Implemented Validation of operational intents + + SCD0005 + Implemented + Off-Nominal planning: down USS + SCD0015 Implemented @@ -117,12 +127,12 @@ USS0005 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents USS0105 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents interuss
.automated_testing
.flight_planning
@@ -133,17 +143,17 @@ DeleteFlightSuccess Implemented - Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ExpectedBehavior Implemented - Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents FlightCoveredByOperationalIntent Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ImplementAPI diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index 964c6d4576..d1cfdf0887 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -449,10 +449,15 @@ ASTM NetRID DSS: ISA Expiry
ASTM NetRID DSS: ISA Subscription Interactions
ASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Submitted ISA Validations
ASTM NetRID DSS: Token Validation - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents + + + DSS0100 + Implemented + Off-Nominal planning: down USS DSS0300 @@ -472,17 +477,17 @@ OPIN0015 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0020 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0025 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents OPIN0030 @@ -494,6 +499,11 @@ Implemented Validation of operational intents + + SCD0005 + Implemented + Off-Nominal planning: down USS + SCD0015 Implemented @@ -547,12 +557,12 @@ USS0005 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents USS0105 Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents interuss
.automated_testing
.flight_planning
@@ -563,17 +573,17 @@ DeleteFlightSuccess Implemented - Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ExpectedBehavior Implemented - Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Flight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents FlightCoveredByOperationalIntent Implemented - Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Validation of operational intents ImplementAPI