diff --git a/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml b/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml index 6ef5c7f17b..c83118d283 100644 --- a/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml +++ b/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml @@ -39,6 +39,18 @@ v1: rid_version: F3411-22a base_url: http://dss.uss2.localutm/rid/v2 has_private_address: true + utm_dss_instances_v21: # TODO discuss if this is the right place for this? The rest seems netrid specific + resource_type: resources.astm.f3548.v21.DSSInstancesResource + dependencies: + auth_adapter: utm_auth + specification: + dss_instances: + - participant_id: uss1 + base_url: http://dss.uss1.localutm/rid/v2 # TODO: probably different base urls here? + has_private_address: true + - participant_id: uss2 + base_url: http://dss.uss2.localutm/rid/v2 + has_private_address: true id_generator: resource_type: resources.interuss.IDGeneratorResource dependencies: @@ -70,6 +82,7 @@ v1: resources: f3411v19_dss_instances: netrid_dss_instances_v19 f3411v22a_dss_instances: netrid_dss_instances_v22a + f3548v21_dss_instances: utm_dss_instances_v21 id_generator: id_generator service_area: service_area artifacts: diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/__init__.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/__init__.py index c0c50ca3fe..6da048f501 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/__init__.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/__init__.py @@ -1 +1 @@ -from .dss import DSSInstanceResource +from .dss import DSSInstanceResource, DSSInstancesResource diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py index ed937b0694..e7088ad90b 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -1,4 +1,4 @@ -from typing import Tuple, List +from typing import Tuple, List, Optional from urllib.parse import urlparse from implicitdict import ImplicitDict @@ -24,6 +24,9 @@ class DSSInstanceSpecification(ImplicitDict): base_url: str """Base URL for the DSS instance according to the ASTM F3548-21 API""" + has_private_address: Optional[bool] + """Whether this DSS instance is expected to have a private address that is not publicly addressable.""" + def __init__(self, *args, **kwargs): super().__init__(**kwargs) try: @@ -34,16 +37,20 @@ def __init__(self, *args, **kwargs): class DSSInstance(object): participant_id: str + has_private_address: bool = False client: infrastructure.UTMClientSession def __init__( self, participant_id: str, base_url: str, + has_private_address: Optional[bool], auth_adapter: infrastructure.AuthAdapter, ): self.participant_id = participant_id self._base_url = base_url + if has_private_address is not None: + self.has_private_address = has_private_address self.client = infrastructure.UTMClientSession(base_url, auth_adapter) def find_op_intent( @@ -92,5 +99,31 @@ def __init__( auth_adapter: AuthAdapterResource, ): self.dss = DSSInstance( - specification.participant_id, specification.base_url, auth_adapter.adapter + specification.participant_id, + specification.base_url, + specification.has_private_address, + auth_adapter.adapter, ) + + +class DSSInstancesSpecification(ImplicitDict): + dss_instances: List[DSSInstanceSpecification] + + +class DSSInstancesResource(Resource[DSSInstancesSpecification]): + dss_instances: List[DSSInstance] + + def __init__( + self, + specification: DSSInstancesSpecification, + auth_adapter: AuthAdapterResource, + ): + self.dss_instances = [ + DSSInstance( + s.participant_id, + s.base_url, + s.has_private_address, + auth_adapter.adapter, + ) + for s in specification.dss_instances + ] diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py index 007c2a8358..9317644cb6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py @@ -5,3 +5,4 @@ from .nominal_planning.conflict_equal_priority_not_permitted.conflict_equal_priority_not_permitted import ( ConflictEqualPriorityNotPermitted, ) +from .dss_interoperability import DSSInteroperability diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.md new file mode 100644 index 0000000000..16ff85c363 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.md @@ -0,0 +1,31 @@ +# ASTM F3548-21 UTM DSS interoperability test scenario + +## Overview + +TODO: Complete with details once we check more than the prerequisites. + +This scenario currently only checks that all specified DSS instances are publicly addressable and reachable. + +## Resources + +### primary_dss_instance + +A resources.astm.f3548.v21.DSSInstanceResource containing the "primary" DSS instance for this scenario. + +### all_dss_instances + +A resources.astm.f3548.v21.DSSInstancesResource containing at least two DSS instances complying with ASTM F3548-21. + +## Prerequisites test case + +### Test environment requirements test step + +#### DSS instance is publicly addressable check + +As per **[astm.f3548.v21.DSS0300](../../../requirements/astm/f3548/v21.md)** the DSS instance should be publicly addressable. +As such, this check will fail if the resolved IP of the DSS host is a private IP address, unless that is explicitly +expected. + +#### DSS instance is reachable check +As per **[astm.f3548.v21.DSS0300](../../../requirements/astm/f3548/v21.md)** the DSS instance should be publicly addressable. +As such, this check will fail if the DSS is not reachable with a dummy query. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py new file mode 100644 index 0000000000..ca87bd6bcd --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py @@ -0,0 +1,95 @@ +import ipaddress +import socket +import time +import uuid +from dataclasses import dataclass +import datetime +from enum import Enum +from typing import List, Dict, Optional +from urllib.parse import urlparse + +import s2sphere + +from monitoring.monitorlib.fetch.rid import ISA +from monitoring.uss_qualifier.common_data_definitions import Severity +from monitoring.uss_qualifier.resources.astm.f3411.dss import ( + DSSInstancesResource, + DSSInstanceResource, +) +from monitoring.uss_qualifier.scenarios.astm.netrid.dss_wrapper import DSSWrapper +from monitoring.uss_qualifier.scenarios.scenario import TestScenario + +VERTICES: List[s2sphere.LatLng] = [ + s2sphere.LatLng.from_degrees(lng=130.6205, lat=-23.6558), + s2sphere.LatLng.from_degrees(lng=130.6301, lat=-23.6898), + s2sphere.LatLng.from_degrees(lng=130.6700, lat=-23.6709), + s2sphere.LatLng.from_degrees(lng=130.6466, lat=-23.6407), +] +SHORT_WAIT_SEC = 5 + + +class DSSInteroperability( + TestScenario +): # TODO needed to extend TestScenario instead of GenericTestScenario otherwise `make format` is unhappy + _dss_primary: DSSWrapper + _dss_others: List[DSSWrapper] + + def __init__( + self, + primary_dss_instance: DSSInstanceResource, + all_dss_instances: DSSInstancesResource, + ): + super().__init__() + self._dss_primary = DSSWrapper(self, primary_dss_instance.dss_instance) + self._dss_others = [ + DSSWrapper(self, dss) + for dss in all_dss_instances.dss_instances + if not dss.is_same_as(primary_dss_instance.dss_instance) + ] + + def run(self): + + self.record_note( + "dss_instances", + f"Provided DSS instances: {[self._dss_primary] + self._dss_others}", + ) + self.begin_test_scenario() + + self.begin_test_case("Prerequisites") + + self.begin_test_step("Test environment requirements") + self._test_env_reqs() + self.end_test_step() + + self.end_test_case() + + self.end_test_scenario() + + def _test_env_reqs(self): + for dss in [self._dss_primary] + self._dss_others: + with self.check( + "DSS instance is publicly addressable", [dss.participant_id] + ) as check: + parsed_url = urlparse(dss.base_url) + ip_addr = socket.gethostbyname(parsed_url.hostname) + + if dss.has_private_address: + self.record_note( + f"{dss.participant_id}_private_address", + f"DSS instance (URL: {dss.base_url}, netloc: {parsed_url.netloc}, resolved IP: {ip_addr}) is declared as explicitly having a private address, skipping check", + ) + elif ipaddress.ip_address(ip_addr).is_private: + check.record_failed( + summary=f"DSS host {parsed_url.netloc} is not publicly addressable", + severity=Severity.Medium, + participants=[dss.participant_id], + details=f"DSS (URL: {dss.base_url}, netloc: {parsed_url.netloc}, resolved IP: {ip_addr}) is not publicly addressable", + ) + + with self.check("DSS instance is reachable", [dss.participant_id]) as check: + # dummy search query + dss.search_subs(check, VERTICES) + + def cleanup(self): + self.begin_cleanup() + self.end_cleanup() diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index 9b83332c44..91c251e487 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -4,11 +4,12 @@ ## [Actions](../../README.md#actions) -1. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) - 1. Scenario: [Validation of operational intents](../../../scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md) ([`scenarios.astm.utm.FlightIntentValidation`](../../../scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py)) +1. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py)) 2. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) - 1. Scenario: [Nominal planning: conflict with higher priority](../../../scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md) ([`scenarios.astm.utm.ConflictHigherPriority`](../../../scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py)) + 1. Scenario: [Validation of operational intents](../../../scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md) ([`scenarios.astm.utm.FlightIntentValidation`](../../../scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py)) 3. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) + 1. Scenario: [Nominal planning: conflict with higher priority](../../../scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md) ([`scenarios.astm.utm.ConflictHigherPriority`](../../../scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py)) +4. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 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)) ## [Checked requirements](../../README.md#checked-requirements) @@ -21,11 +22,16 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + + DSS0300 + Implemented + ASTM F3548-21 UTM DSS interoperability + GEN0310 Implemented diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml index 51380c2746..ec85035680 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml @@ -2,12 +2,18 @@ name: ASTM F3548-21 resources: flight_planners: resources.flight_planning.FlightPlannersResource dss: resources.astm.f3548.v21.DSSInstanceResource + all_dss_instances: resources.astm.f3548.DSSInstancesResource? conflicting_flights: resources.flight_planning.FlightIntentsResource priority_preemption_flights: resources.flight_planning.FlightIntentsResource invalid_flight_intents: resources.flight_planning.FlightIntentsResource nominal_planning_selector: resources.flight_planning.FlightPlannerCombinationSelectorResource? priority_planning_selector: resources.flight_planning.FlightPlannerCombinationSelectorResource? actions: +- test_scenario: # TODO confirm this should go here + scenario_type: scenarios.astm.utm.DSSInteroperability + resources: + primary_dss_instance: dss + all_dss_instances: all_dss_instances - action_generator: generator_type: action_generators.flight_planning.FlightPlannerCombinations resources: diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index 050dde31a4..4296159689 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md @@ -18,11 +18,16 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + + DSS0300 + Implemented + ASTM F3548-21 UTM DSS interoperability + GEN0310 Implemented diff --git a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.yaml b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.yaml index b8fad053be..0c33ce19c7 100644 --- a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.yaml +++ b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.yaml @@ -2,6 +2,7 @@ name: ASTM DSS tests resources: f3411v19_dss_instances: resources.astm.f3411.DSSInstancesResource f3411v22a_dss_instances: resources.astm.f3411.DSSInstancesResource + f3548v21_dss_instances: resources.astm.f3548.v21.DSSInstancesResource id_generator: resources.interuss.IDGeneratorResource service_area: resources.netrid.ServiceAreaResource actions: diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index f7dd88ba0a..659b1a805d 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.md +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.md @@ -18,11 +18,16 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + + DSS0300 + Implemented + ASTM F3548-21 UTM DSS interoperability + GEN0310 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index a690bb7ab8..7997e33f9c 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -388,11 +388,16 @@ ASTM NetRID DSS: Simple ISA - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents + + DSS0300 + Implemented + ASTM F3548-21 UTM DSS interoperability + GEN0310 Implemented