From 7dc6b9f1338b64b0b57a8b8d381434ded1d9b0b5 Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Thu, 12 Oct 2023 20:26:46 +0200 Subject: [PATCH] [uss_qualifier] DSS0300 (utm) setup check for basic requirements (#236) UTM DSS0300 --- .../action_generators/astm/f3548/__init__.py | 1 + .../astm/f3548/for_each_dss.py | 97 +++++++++++++++++++ .../configurations/dev/f3548.yaml | 1 + .../dev/library/environment.yaml | 13 +++ .../resources/astm/f3548/v21/__init__.py | 2 +- .../resources/astm/f3548/v21/dss.py | 54 ++++++++++- .../scenarios/astm/utm/__init__.py | 1 + .../astm/utm/dss_interoperability.md | 31 ++++++ .../astm/utm/dss_interoperability.py | 83 ++++++++++++++++ .../suites/astm/utm/dss_probing.md | 24 +++++ .../suites/astm/utm/dss_probing.yaml | 11 +++ .../uss_qualifier/suites/astm/utm/f3548_21.md | 15 ++- .../suites/astm/utm/f3548_21.yaml | 16 +++ .../suites/faa/uft/message_signing.md | 7 +- .../suites/uspace/flight_auth.md | 7 +- .../suites/uspace/required_services.md | 7 +- 16 files changed, 359 insertions(+), 11 deletions(-) create mode 100644 monitoring/uss_qualifier/action_generators/astm/f3548/__init__.py create mode 100644 monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.md create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py create mode 100644 monitoring/uss_qualifier/suites/astm/utm/dss_probing.md create mode 100644 monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml diff --git a/monitoring/uss_qualifier/action_generators/astm/f3548/__init__.py b/monitoring/uss_qualifier/action_generators/astm/f3548/__init__.py new file mode 100644 index 0000000000..4d6b15a8b1 --- /dev/null +++ b/monitoring/uss_qualifier/action_generators/astm/f3548/__init__.py @@ -0,0 +1 @@ +from .for_each_dss import ForEachDSS diff --git a/monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py b/monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py new file mode 100644 index 0000000000..2bce45751e --- /dev/null +++ b/monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py @@ -0,0 +1,97 @@ +from typing import Dict, List, Optional + +from implicitdict import ImplicitDict + +from monitoring.monitorlib.inspection import fullname +from monitoring.uss_qualifier.action_generators.documentation.definitions import ( + PotentialGeneratedAction, +) +from monitoring.uss_qualifier.action_generators.documentation.documentation import ( + list_potential_actions_for_action_declaration, +) +from monitoring.uss_qualifier.reports.report import TestSuiteActionReport +from monitoring.uss_qualifier.resources.astm.f3548.v21 import ( + DSSInstancesResource, + DSSInstanceResource, +) + +from monitoring.uss_qualifier.resources.definitions import ResourceID +from monitoring.uss_qualifier.resources.resource import ( + ResourceType, + MissingResourceError, +) +from monitoring.uss_qualifier.suites.definitions import TestSuiteActionDeclaration +from monitoring.uss_qualifier.suites.suite import ( + ActionGenerator, + TestSuiteAction, + ReactionToFailure, +) + + +class ForEachDSSSpecification(ImplicitDict): + action_to_repeat: TestSuiteActionDeclaration + """Test suite action to run for each DSS instance""" + + dss_instances_source: ResourceID + """ID of the resource providing the single DSS instance""" + + dss_instance_id: ResourceID + """Resource IDs of DSS input to the action_to_repeat""" + + +class ForEachDSS(ActionGenerator[ForEachDSSSpecification]): + _actions: List[TestSuiteAction] + _current_action: int + _failure_reaction: ReactionToFailure + + @classmethod + def list_potential_actions( + cls, specification: ForEachDSSSpecification + ) -> List[PotentialGeneratedAction]: + return list_potential_actions_for_action_declaration( + specification.action_to_repeat + ) + + def __init__( + self, + specification: ForEachDSSSpecification, + resources: Dict[ResourceID, ResourceType], + ): + if specification.dss_instances_source not in resources: + raise MissingResourceError( + f"Resource ID {specification.dss_instances_source} specified as `dss_instances_source` was not present in the available resource pool", + specification.dss_instances_source, + ) + dss_instances_resource: DSSInstancesResource = resources[ + specification.dss_instances_source + ] + if not isinstance(dss_instances_resource, DSSInstancesResource): + raise ValueError( + f"Expected resource ID {specification.dss_instances_source} to be a {fullname(DSSInstancesResource)} but it was a {fullname(dss_instances_resource.__class__)} instead" + ) + dss_instances = dss_instances_resource.dss_instances + + self._actions = [] + for dss_instance in dss_instances: + modified_resources = {k: v for k, v in resources.items()} + modified_resources[ + specification.dss_instance_id + ] = DSSInstanceResource.from_dss_instance(dss_instance) + + self._actions.append( + TestSuiteAction(specification.action_to_repeat, modified_resources) + ) + + self._current_action = 0 + self._failure_reaction = specification.action_to_repeat.on_failure + + def run_next_action(self) -> Optional[TestSuiteActionReport]: + if self._current_action < len(self._actions): + report = self._actions[self._current_action].run() + self._current_action += 1 + if not report.successful(): + if self._failure_reaction == ReactionToFailure.Abort: + self._current_action = len(self._actions) + return report + else: + return None diff --git a/monitoring/uss_qualifier/configurations/dev/f3548.yaml b/monitoring/uss_qualifier/configurations/dev/f3548.yaml index 257a49d4fc..981483f45b 100644 --- a/monitoring/uss_qualifier/configurations/dev/f3548.yaml +++ b/monitoring/uss_qualifier/configurations/dev/f3548.yaml @@ -13,6 +13,7 @@ v1: priority_preemption_flights: priority_preemption_flights invalid_flight_intents: invalid_flight_intents dss: dss + dss_instances: dss_instances artifacts: tested_roles: report_path: output/tested_roles_f3548 diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment.yaml index 3aaf1f9b14..8835e64f9d 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment.yaml @@ -147,6 +147,19 @@ f3548: participant_id: uss1 base_url: http://dss.uss1.localutm has_private_address: true + dss_instances: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.astm.f3548.v21.DSSInstancesResource + dependencies: + auth_adapter: utm_auth + specification: + dss_instances: + - participant_id: uss1 + base_url: http://dss.uss1.localutm + has_private_address: true + - participant_id: uss2 + base_url: http://dss.uss2.localutm + has_private_address: true f3548_single_scenario: uss1: 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..c0b99aac18 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -1,4 +1,5 @@ -from typing import Tuple, List +from __future__ import annotations +from typing import Tuple, List, Optional from urllib.parse import urlparse from implicitdict import ImplicitDict @@ -24,6 +25,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 +38,21 @@ def __init__(self, *args, **kwargs): class DSSInstance(object): participant_id: str + base_url: 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 + 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( @@ -82,6 +91,13 @@ def get_full_op_intent( ).operational_intent return result, query + def is_same_as(self, other: DSSInstance) -> bool: + return ( + self.participant_id == other.participant_id + and self.base_url == other.base_url + and self.has_private_address == other.has_private_address + ) + class DSSInstanceResource(Resource[DSSInstanceSpecification]): dss: DSSInstance @@ -92,5 +108,37 @@ def __init__( auth_adapter: AuthAdapterResource, ): self.dss = DSSInstance( - specification.participant_id, specification.base_url, auth_adapter.adapter + specification.participant_id, + specification.base_url, + specification.get("has_private_address"), + auth_adapter.adapter, ) + + @classmethod + def from_dss_instance(cls, dss_instance: DSSInstance) -> DSSInstanceResource: + self = cls.__new__(cls) + self.dss = dss_instance + return self + + +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..5dab518aad --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py @@ -0,0 +1,83 @@ +import ipaddress +import socket +from typing import List +from urllib.parse import urlparse + +from uas_standards.astm.f3548.v21.api import Volume4D, Volume3D, Polygon, LatLngPoint + +from monitoring.uss_qualifier.common_data_definitions import Severity +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( + DSSInstancesResource, + DSSInstanceResource, + DSSInstance, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario + +VERTICES: List[LatLngPoint] = [ + LatLngPoint(lng=130.6205, lat=-23.6558), + LatLngPoint(lng=130.6301, lat=-23.6898), + LatLngPoint(lng=130.6700, lat=-23.6709), + LatLngPoint(lng=130.6466, lat=-23.6407), +] +SHORT_WAIT_SEC = 5 + + +class DSSInteroperability(TestScenario): + _dss_primary: DSSInstance + _dss_others: List[DSSInstance] + + def __init__( + self, + primary_dss_instance: DSSInstanceResource, + all_dss_instances: DSSInstancesResource, + ): + super().__init__() + self._dss_primary = primary_dss_instance.dss + self._dss_others = [ + dss + for dss in all_dss_instances.dss_instances + if not dss.is_same_as(primary_dss_instance.dss) + ] + + def run(self): + + 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.find_op_intent( + extent=Volume4D( + volume=Volume3D(outline_polygon=Polygon(vertices=VERTICES)) + ) + ) diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md new file mode 100644 index 0000000000..b349b5c1da --- /dev/null +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md @@ -0,0 +1,24 @@ + +# DSS testing for ASTM NetRID F3558-21 test suite +[`suites.astm.utm.dss_probing`](./dss_probing.yaml) + +## [Actions](../../README.md#actions) + +1. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py)) + +## [Checked requirements](../../README.md#checked-requirements) + + + + + + + + + + + + + + +
PackageRequirementStatusChecked in
astm
.f3548
.v21
DSS0300ImplementedASTM F3548-21 UTM DSS interoperability
diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml new file mode 100644 index 0000000000..ed18ccaf34 --- /dev/null +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml @@ -0,0 +1,11 @@ +name: DSS testing for ASTM NetRID F3558-21 +resources: + dss: resources.astm.f3548.v21.DSSInstanceResource + all_dss_instances: resources.astm.f3548.v21.DSSInstancesResource? +actions: + - test_scenario: + scenario_type: scenarios.astm.utm.DSSInteroperability + resources: + primary_dss_instance: dss + all_dss_instances: all_dss_instances + diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index 9b83332c44..c0a8dfea73 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -4,11 +4,13 @@ ## [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. Action generator: [`action_generators.astm.f3548.ForEachDSS`](../../../action_generators/astm/f3548/for_each_dss.py) + 1. Suite: [DSS testing for ASTM NetRID F3558-21](dss_probing.md) ([`suites.astm.utm.dss_probing`](dss_probing.yaml)) 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 +23,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..db4afe785f 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml @@ -2,12 +2,28 @@ name: ASTM F3548-21 resources: flight_planners: resources.flight_planning.FlightPlannersResource dss: resources.astm.f3548.v21.DSSInstanceResource + dss_instances: resources.astm.f3548.v21.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: +- action_generator: + generator_type: action_generators.astm.f3548.ForEachDSS + resources: + dss_instances: dss_instances + specification: + action_to_repeat: + test_suite: + suite_type: suites.astm.utm.dss_probing + resources: + dss: dss + all_dss_instances: dss_instances + on_failure: Continue + dss_instances_source: dss_instances + dss_instance_id: dss + on_failure: Continue - 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/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