diff --git a/github_pages/static/index.md b/github_pages/static/index.md index 02cd5b3ce0..9b9bf2290b 100644 --- a/github_pages/static/index.md +++ b/github_pages/static/index.md @@ -33,3 +33,8 @@ These reports were generated during continuous integration for the most recent P * [Sequence view](./artifacts/uss_qualifier/reports/dss_probing/sequence) * [Tested requirements](./artifacts/uss_qualifier/reports/dss_probing/requirements) + +### [General flight authorization configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/general_flight_auth.yaml) + +* [Sequence view](./artifacts/uss_qualifier/reports/general_flight_auth/sequence) +* [Tested requirements](./artifacts/uss_qualifier/reports/general_flight_auth/requirements) diff --git a/monitoring/mock_uss/README.md b/monitoring/mock_uss/README.md index 91488cc899..f770ba3e0e 100644 --- a/monitoring/mock_uss/README.md +++ b/monitoring/mock_uss/README.md @@ -18,9 +18,10 @@ The available functionality sets are: * [`msgsigning`](msgsigning): [IETF HTTP Message Signatures](https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/) * [`riddp`](riddp): Remote ID Display Provider * [`ridsp`](ridsp): Remote ID Service Provider -* [`scdsc`](scdsc): ASTM F3548 strategic coordinator +* `scdsc`: Combination of [ASTM F3548-21](f3548v21) strategic conflict detection and [scd flight injection](scd_injection) +* [`flight_planning`](flight_planning): Exposes [InterUSS flight_planning automated testing API](https://github.com/interuss/automated_testing_interfaces/tree/main/flight_planning) * [`tracer`](tracer): Interoperability ecosystem tracer logger -* [`interaction_logging`](interaction_logging): Enables logging of the [interuss](https://github.com/astm-utm/Protocol/blob/master/utm.yaml) interactions between mock_uss and other uss participants +* [`interaction_logging`](interaction_logging): Enables logging of interactions between mock_uss and other uss participants ## Local deployment diff --git a/monitoring/mock_uss/__init__.py b/monitoring/mock_uss/__init__.py index 7733a0829b..4aa3c4ab35 100644 --- a/monitoring/mock_uss/__init__.py +++ b/monitoring/mock_uss/__init__.py @@ -81,8 +81,8 @@ def require_config_value(config_key: str) -> None: if SERVICE_SCDSC in webapp.config[config.KEY_SERVICES]: enabled_services.add(SERVICE_SCDSC) - from monitoring.mock_uss import scdsc - from monitoring.mock_uss.scdsc import routes as scdsc_routes + from monitoring.mock_uss.f3548v21 import routes_scd + from monitoring.mock_uss.scd_injection import routes as scd_injection_routes if SERVICE_MESSAGESIGNING in webapp.config[config.KEY_SERVICES]: enabled_services.add(SERVICE_MESSAGESIGNING) diff --git a/monitoring/mock_uss/f3548v21/README.md b/monitoring/mock_uss/f3548v21/README.md new file mode 100644 index 0000000000..9f7137ca84 --- /dev/null +++ b/monitoring/mock_uss/f3548v21/README.md @@ -0,0 +1,3 @@ +# mock_uss: ASTM F3548-21 + +[ASTM F3548-21](http://astm.org/f3548-21.html) standardizes UTM interoperability between USSs to achieve strategic coordination and communicate constraints. This folder enables [mock_uss](..) to comply with the Strategic Conflict Detection requirements from that standard. diff --git a/monitoring/mock_uss/scdsc/__init__.py b/monitoring/mock_uss/f3548v21/__init__.py similarity index 100% rename from monitoring/mock_uss/scdsc/__init__.py rename to monitoring/mock_uss/f3548v21/__init__.py diff --git a/monitoring/mock_uss/scdsc/flight_planning.py b/monitoring/mock_uss/f3548v21/flight_planning.py similarity index 88% rename from monitoring/mock_uss/scdsc/flight_planning.py rename to monitoring/mock_uss/f3548v21/flight_planning.py index d0c9457fa9..c477ce1067 100644 --- a/monitoring/mock_uss/scdsc/flight_planning.py +++ b/monitoring/mock_uss/f3548v21/flight_planning.py @@ -2,14 +2,16 @@ from typing import Optional, List, Callable import arrow + +from monitoring.uss_qualifier.resources.overrides import apply_overrides from uas_standards.astm.f3548.v21 import api as f3548_v21 +from uas_standards.astm.f3548.v21.api import OperationalIntentDetails, OperationalIntent from uas_standards.astm.f3548.v21.constants import OiMaxVertices, OiMaxPlanHorizonDays from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api -from monitoring.mock_uss.scdsc.database import FlightRecord +from monitoring.mock_uss.flights.database import FlightRecord from monitoring.monitorlib.geotemporal import Volume4DCollection from monitoring.monitorlib.locality import Locality -from monitoring.monitorlib.uspace import problems_with_flight_authorisation from uas_standards.interuss.automated_testing.scd.v1.api import OperationalIntentState @@ -17,19 +19,13 @@ class PlanningError(Exception): pass -def validate_request(req_body: scd_api.InjectFlightRequest, locality: Locality) -> None: +def validate_request(req_body: scd_api.InjectFlightRequest) -> None: """Raise a PlannerError if the request is not valid. Args: req_body: Information about the requested flight. locality: Jurisdictional requirements which the mock_uss should follow. """ - if locality.is_uspace_applicable(): - # Validate flight authorisation - problems = problems_with_flight_authorisation(req_body.flight_authorisation) - if problems: - raise PlanningError(", ".join(problems)) - # Validate max number of vertices nb_vertices = 0 for volume in ( @@ -219,3 +215,23 @@ def op_intent_transition_valid( else: return False + + +def op_intent_from_flightrecord(flight: FlightRecord, method: str) -> OperationalIntent: + ref = flight.op_intent.reference + details = OperationalIntentDetails( + volumes=flight.op_intent.details.volumes, + off_nominal_volumes=flight.op_intent.details.off_nominal_volumes, + priority=flight.op_intent.details.priority, + ) + op_intent = OperationalIntent(reference=ref, details=details) + if flight.mod_op_sharing_behavior: + mod_op_sharing_behavior = flight.mod_op_sharing_behavior + if mod_op_sharing_behavior.modify_sharing_methods is not None: + if method not in mod_op_sharing_behavior.modify_sharing_methods: + return op_intent + op_intent = apply_overrides( + op_intent, mod_op_sharing_behavior.modify_fields, parse_result=False + ) + + return op_intent diff --git a/monitoring/mock_uss/scdsc/routes_scdsc.py b/monitoring/mock_uss/f3548v21/routes_scd.py similarity index 66% rename from monitoring/mock_uss/scdsc/routes_scdsc.py rename to monitoring/mock_uss/f3548v21/routes_scd.py index 4b2157a51e..0bb78bc04d 100644 --- a/monitoring/mock_uss/scdsc/routes_scdsc.py +++ b/monitoring/mock_uss/f3548v21/routes_scd.py @@ -1,19 +1,12 @@ -import json - import flask -from implicitdict import ImplicitDict + +from monitoring.mock_uss.f3548v21.flight_planning import op_intent_from_flightrecord from monitoring.monitorlib import scd from monitoring.mock_uss import webapp from monitoring.mock_uss.auth import requires_scope -from monitoring.mock_uss.scdsc.database import db -from monitoring.mock_uss.scdsc.database import FlightRecord -from monitoring.uss_qualifier.resources.overrides import ( - apply_overrides, -) +from monitoring.mock_uss.flights.database import db from uas_standards.astm.f3548.v21.api import ( ErrorResponse, - OperationalIntent, - OperationalIntentDetails, GetOperationalIntentDetailsResponse, ) @@ -51,26 +44,6 @@ def scdsc_get_operational_intent_details(entityid: str): return flask.jsonify(response), 200 -def op_intent_from_flightrecord(flight: FlightRecord, method: str) -> OperationalIntent: - ref = flight.op_intent.reference - details = OperationalIntentDetails( - volumes=flight.op_intent.details.volumes, - off_nominal_volumes=flight.op_intent.details.off_nominal_volumes, - priority=flight.op_intent.details.priority, - ) - op_intent = OperationalIntent(reference=ref, details=details) - if flight.mod_op_sharing_behavior: - mod_op_sharing_behavior = flight.mod_op_sharing_behavior - if mod_op_sharing_behavior.modify_sharing_methods is not None: - if method not in mod_op_sharing_behavior.modify_sharing_methods: - return op_intent - op_intent = apply_overrides( - op_intent, mod_op_sharing_behavior.modify_fields, parse_result=False - ) - - return op_intent - - @webapp.route("/mock/scd/uss/v1/operational_intents", methods=["POST"]) @requires_scope(scd.SCOPE_SC) def scdsc_notify_operational_intent_details_changed(): diff --git a/monitoring/mock_uss/flight_planning/README.md b/monitoring/mock_uss/flight_planning/README.md new file mode 100644 index 0000000000..c62c7a9b2b --- /dev/null +++ b/monitoring/mock_uss/flight_planning/README.md @@ -0,0 +1,3 @@ +# mock_uss: flight_planner + +This folder contains materials implementing [InterUSS's flight_planning automated testing interface](https://github.com/interuss/automated_testing_interfaces/tree/main/flight_planning) by [mock_uss](..). diff --git a/monitoring/mock_uss/flight_planning/routes.py b/monitoring/mock_uss/flight_planning/routes.py index 54f21caf15..0c7d90cf97 100644 --- a/monitoring/mock_uss/flight_planning/routes.py +++ b/monitoring/mock_uss/flight_planning/routes.py @@ -6,7 +6,7 @@ from implicitdict import ImplicitDict from loguru import logger -from monitoring.mock_uss.scdsc.routes_injection import ( +from monitoring.mock_uss.scd_injection.routes_injection import ( inject_flight, lock_flight, release_flight_lock, diff --git a/monitoring/mock_uss/flights/README.md b/monitoring/mock_uss/flights/README.md new file mode 100644 index 0000000000..05f35b8307 --- /dev/null +++ b/monitoring/mock_uss/flights/README.md @@ -0,0 +1,3 @@ +# mock_uss: flights + +This folder contains materials related to generic handling of user-requested flights by [mock_uss](..). diff --git a/monitoring/mock_uss/flights/__init__.py b/monitoring/mock_uss/flights/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/mock_uss/scdsc/database.py b/monitoring/mock_uss/flights/database.py similarity index 93% rename from monitoring/mock_uss/scdsc/database.py rename to monitoring/mock_uss/flights/database.py index 94892eadbe..aefeb2b9c7 100644 --- a/monitoring/mock_uss/scdsc/database.py +++ b/monitoring/mock_uss/flights/database.py @@ -1,4 +1,5 @@ import json +from datetime import timedelta from typing import Dict, Optional from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo @@ -11,6 +12,8 @@ MockUssFlightBehavior, ) +DEADLOCK_TIMEOUT = timedelta(seconds=5) + class FlightRecord(ImplicitDict): """Representation of a flight in a USS""" diff --git a/monitoring/mock_uss/flights/planning.py b/monitoring/mock_uss/flights/planning.py new file mode 100644 index 0000000000..cc07cfd4bf --- /dev/null +++ b/monitoring/mock_uss/flights/planning.py @@ -0,0 +1,47 @@ +import time +from datetime import datetime +from typing import Callable + +from monitoring.mock_uss.flights.database import FlightRecord, db, DEADLOCK_TIMEOUT + + +def lock_flight(flight_id: str, log: Callable[[str], None]) -> FlightRecord: + # If this is a change to an existing flight, acquire lock to that flight + log(f"Acquiring lock for flight {flight_id}") + deadline = datetime.utcnow() + DEADLOCK_TIMEOUT + while True: + with db as tx: + if flight_id in tx.flights: + # This is an existing flight being modified + existing_flight = tx.flights[flight_id] + if existing_flight and not existing_flight.locked: + log("Existing flight locked for update") + existing_flight.locked = True + break + else: + log("Request is for a new flight (lock established)") + tx.flights[flight_id] = None + existing_flight = None + break + # We found an existing flight but it was locked; wait for it to become + # available + time.sleep(0.5) + + if datetime.utcnow() > deadline: + raise RuntimeError( + f"Deadlock in inject_flight while attempting to gain access to flight {flight_id}" + ) + return existing_flight + + +def release_flight_lock(flight_id: str, log: Callable[[str], None]) -> None: + with db as tx: + if flight_id in tx.flights: + if tx.flights[flight_id]: + # FlightRecord was a true existing flight + log(f"Releasing lock on existing flight_id {flight_id}") + tx.flights[flight_id].locked = False + else: + # FlightRecord was just a placeholder for a new flight + log(f"Releasing placeholder for existing flight_id {flight_id}") + del tx.flights[flight_id] diff --git a/monitoring/mock_uss/scd_injection/README.md b/monitoring/mock_uss/scd_injection/README.md new file mode 100644 index 0000000000..a6d634d284 --- /dev/null +++ b/monitoring/mock_uss/scd_injection/README.md @@ -0,0 +1,3 @@ +# mock_uss: scd_injection + +This folder contains material related to the deprecated [InterUSS scd automated testing interface](https://github.com/interuss/automated_testing_interfaces/tree/main/scd). diff --git a/monitoring/mock_uss/scd_injection/__init__.py b/monitoring/mock_uss/scd_injection/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/mock_uss/scd_injection/routes.py b/monitoring/mock_uss/scd_injection/routes.py new file mode 100644 index 0000000000..837bd9d2cb --- /dev/null +++ b/monitoring/mock_uss/scd_injection/routes.py @@ -0,0 +1,6 @@ +from monitoring.mock_uss import webapp + + +@webapp.route("/scdsc/status") +def scdsc_status(): + return "scd flight injection API ok" diff --git a/monitoring/mock_uss/scdsc/routes_injection.py b/monitoring/mock_uss/scd_injection/routes_injection.py similarity index 91% rename from monitoring/mock_uss/scdsc/routes_injection.py rename to monitoring/mock_uss/scd_injection/routes_injection.py index 1622fa76cc..db7e8d7afe 100644 --- a/monitoring/mock_uss/scdsc/routes_injection.py +++ b/monitoring/mock_uss/scd_injection/routes_injection.py @@ -2,7 +2,7 @@ import traceback from datetime import datetime, timedelta import time -from typing import List, Tuple, Callable, Optional +from typing import List, Tuple, Optional import uuid import flask @@ -10,6 +10,8 @@ from loguru import logger import requests.exceptions +from monitoring.mock_uss.flights.planning import lock_flight, release_flight_lock +from monitoring.mock_uss.f3548v21 import utm_client from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo from uas_standards.astm.f3548.v21 import api from uas_standards.astm.f3548.v21.api import ( @@ -32,19 +34,20 @@ OperationalIntentState, ) -from monitoring.mock_uss import webapp, require_config_value +from monitoring.mock_uss import webapp, require_config_value, uspace from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.config import KEY_BASE_URL from monitoring.mock_uss.dynamic_configuration.configuration import get_locality -from monitoring.mock_uss.scdsc import database, utm_client -from monitoring.mock_uss.scdsc.database import db, FlightRecord -from monitoring.mock_uss.scdsc.flight_planning import ( +from monitoring.mock_uss.flights import database +from monitoring.mock_uss.flights.database import db, FlightRecord +from monitoring.mock_uss.f3548v21.flight_planning import ( validate_request, check_for_disallowed_conflicts, PlanningError, + op_intent_from_flightrecord, op_intent_transition_valid, ) -from monitoring.mock_uss.scdsc.routes_scdsc import op_intent_from_flightrecord +import monitoring.mock_uss.uspace.flight_auth from monitoring.monitorlib import versioning from monitoring.monitorlib.clients import scd as scd_client from monitoring.monitorlib.fetch import QueryError @@ -188,48 +191,6 @@ def _mock_uss_flight_behavior_in_req( return None -def lock_flight(flight_id: str, log: Callable[[str], None]) -> FlightRecord: - # If this is a change to an existing flight, acquire lock to that flight - log(f"Acquiring lock for flight {flight_id}") - deadline = datetime.utcnow() + DEADLOCK_TIMEOUT - while True: - with db as tx: - if flight_id in tx.flights: - # This is an existing flight being modified - existing_flight = tx.flights[flight_id] - if existing_flight and not existing_flight.locked: - log("Existing flight locked for update") - existing_flight.locked = True - break - else: - log("Request is for a new flight (lock established)") - tx.flights[flight_id] = None - existing_flight = None - break - # We found an existing flight but it was locked; wait for it to become - # available - time.sleep(0.5) - - if datetime.utcnow() > deadline: - raise RuntimeError( - f"Deadlock in inject_flight while attempting to gain access to flight {flight_id}" - ) - return existing_flight - - -def release_flight_lock(flight_id: str, log: Callable[[str], None]) -> None: - with db as tx: - if flight_id in tx.flights: - if tx.flights[flight_id]: - # FlightRecord was a true existing flight - log(f"Releasing lock on existing flight_id {flight_id}") - tx.flights[flight_id].locked = False - else: - # FlightRecord was just a placeholder for a new flight - log(f"Releasing placeholder for existing flight_id {flight_id}") - del tx.flights[flight_id] - - def inject_flight( flight_id: str, req_body: MockUSSInjectFlightRequest, @@ -244,7 +205,9 @@ def log(msg: str): # Validate request log("Validating request") try: - validate_request(req_body, locality) + if locality.is_uspace_applicable(): + uspace.flight_auth.validate_request(req_body) + validate_request(req_body) except PlanningError as e: return ( InjectFlightResponse( diff --git a/monitoring/mock_uss/scdsc/README.md b/monitoring/mock_uss/scdsc/README.md deleted file mode 100644 index 1786fb2364..0000000000 --- a/monitoring/mock_uss/scdsc/README.md +++ /dev/null @@ -1 +0,0 @@ -[ASTM F3548-21](http://astm.org/f3548-21.html) standardizes UTM interoperability between USSs to achieve strategic coordination and communicate constraints. When this `scdsc` [mock_uss](..) functionality is enabled, mock_uss will behave like an F3548-21 strategic coordinator that accepts user flight planning attempts via the [InterUSS scd automated testing interface](../../../interfaces/automated_testing/scd) and attempts to establish and communicate an operational intent for those flights according to ASTM F3548-21. diff --git a/monitoring/mock_uss/scdsc/routes.py b/monitoring/mock_uss/scdsc/routes.py deleted file mode 100644 index 6c85debe12..0000000000 --- a/monitoring/mock_uss/scdsc/routes.py +++ /dev/null @@ -1,10 +0,0 @@ -from monitoring.mock_uss import webapp - - -@webapp.route("/scdsc/status") -def scdsc_status(): - return "Mock SCD strategic coordinator ok" - - -from . import routes_scdsc -from . import routes_injection diff --git a/monitoring/mock_uss/uspace/README.md b/monitoring/mock_uss/uspace/README.md new file mode 100644 index 0000000000..5bc1871b38 --- /dev/null +++ b/monitoring/mock_uss/uspace/README.md @@ -0,0 +1,3 @@ +# mock_uss: uspace + +This folder contains materials allowing mock_uss to emulate behaviors required in U-space. diff --git a/monitoring/mock_uss/uspace/__init__.py b/monitoring/mock_uss/uspace/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/mock_uss/uspace/flight_auth.py b/monitoring/mock_uss/uspace/flight_auth.py new file mode 100644 index 0000000000..5d6b3c989c --- /dev/null +++ b/monitoring/mock_uss/uspace/flight_auth.py @@ -0,0 +1,14 @@ +from monitoring.mock_uss.f3548v21.flight_planning import PlanningError +from monitoring.monitorlib.uspace import problems_with_flight_authorisation +from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api + + +def validate_request(req_body: scd_api.InjectFlightRequest) -> None: + """Raise a PlannerError if the request is not valid. + + Args: + req_body: Information about the requested flight. + """ + problems = problems_with_flight_authorisation(req_body.flight_authorisation) + if problems: + raise PlanningError(", ".join(problems)) diff --git a/monitoring/monitorlib/clients/flight_planning/client_v1.py b/monitoring/monitorlib/clients/flight_planning/client_v1.py index 8fa3cf32bb..ce91c33182 100644 --- a/monitoring/monitorlib/clients/flight_planning/client_v1.py +++ b/monitoring/monitorlib/clients/flight_planning/client_v1.py @@ -19,9 +19,10 @@ from monitoring.monitorlib.clients.flight_planning.planning import ( PlanningActivityResponse, ) -from monitoring.monitorlib.fetch import query_and_describe +from monitoring.monitorlib.fetch import query_and_describe, QueryType from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.uss_qualifier.configurations.configuration import ParticipantID from uas_standards.interuss.automated_testing.flight_planning.v1 import api from uas_standards.interuss.automated_testing.flight_planning.v1.constants import Scope @@ -29,9 +30,11 @@ class V1FlightPlannerClient(FlightPlannerClient): _session: UTMClientSession + _participant_id: ParticipantID - def __init__(self, session: UTMClientSession): + def __init__(self, session: UTMClientSession, participant_id: ParticipantID): self._session = session + self._participant_id = participant_id def _inject( self, @@ -53,7 +56,13 @@ def _inject( op = api.OPERATIONS[api.OperationID.UpsertFlightPlan] url = op.path.format(flight_plan_id=flight_plan_id) query = query_and_describe( - self._session, op.verb, url, json=req, scope=Scope.Plan + self._session, + op.verb, + url, + json=req, + scope=Scope.Plan, + participant_id=self._participant_id, + query_type=QueryType.InterUSSFlightPlanningV1UpsertFlightPlan, ) if query.status_code != 200 and query.status_code != 201: raise PlanningActivityError( @@ -108,7 +117,14 @@ def try_end_flight( ) op = api.OPERATIONS[api.OperationID.DeleteFlightPlan] url = op.path.format(flight_plan_id=flight_id) - query = query_and_describe(self._session, op.verb, url, scope=Scope.Plan) + query = query_and_describe( + self._session, + op.verb, + url, + scope=Scope.Plan, + participant_id=self._participant_id, + query_type=QueryType.InterUSSFlightPlanningV1DeleteFlightPlan, + ) if query.status_code != 200: raise PlanningActivityError( f"Attempt to delete flight plan returned status {query.status_code} rather than 200 as expected", @@ -134,7 +150,12 @@ def try_end_flight( def report_readiness(self) -> TestPreparationActivityResponse: op = api.OPERATIONS[api.OperationID.GetStatus] query = query_and_describe( - self._session, op.verb, op.path, scope=Scope.DirectAutomatedTest + self._session, + op.verb, + op.path, + scope=Scope.DirectAutomatedTest, + participant_id=self._participant_id, + query_type=QueryType.InterUSSFlightPlanningV1GetStatus, ) if query.status_code != 200: raise PlanningActivityError( @@ -166,7 +187,13 @@ def clear_area(self, area: Volume4D) -> TestPreparationActivityResponse: op = api.OPERATIONS[api.OperationID.ClearArea] query = query_and_describe( - self._session, op.verb, op.path, json=req, scope=Scope.DirectAutomatedTest + self._session, + op.verb, + op.path, + json=req, + scope=Scope.DirectAutomatedTest, + participant_id=self._participant_id, + query_type=QueryType.InterUSSFlightPlanningV1ClearArea, ) if query.status_code != 200: raise PlanningActivityError( diff --git a/monitoring/monitorlib/fetch/__init__.py b/monitoring/monitorlib/fetch/__init__.py index 1661f0eb33..d69a3eb321 100644 --- a/monitoring/monitorlib/fetch/__init__.py +++ b/monitoring/monitorlib/fetch/__init__.py @@ -221,6 +221,20 @@ class QueryType(str, Enum): # InterUSS automated testing versioning interface InterUSSVersioningGetVersion = "interuss.automated_testing.versioning.GetVersion" + # InterUSS automated testing flight_planning interface + InterUSSFlightPlanningV1GetStatus = ( + "interuss.automated_testing.flight_planning.v1.GetStatus" + ) + InterUSSFlightPlanningV1ClearArea = ( + "interuss.automated_testing.flight_planning.v1.ClearArea" + ) + InterUSSFlightPlanningV1UpsertFlightPlan = ( + "interuss.automated_testing.flight_planning.v1.UpsertFlightPlan" + ) + InterUSSFlightPlanningV1DeleteFlightPlan = ( + "interuss.automated_testing.flight_planning.v1.DeleteFlightPlan" + ) + @staticmethod def flight_details(rid_version: RIDVersion): if rid_version == RIDVersion.f3411_19: diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index e6b26c046e..2c1142a54e 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -603,6 +603,28 @@ def isa_url(self) -> str: f"Cannot retrieve isa_url using RID version {self.rid_version}" ) + @property + def notification_index(self) -> int: + if self.rid_version == RIDVersion.f3411_19: + return self.v19_value.notification_index + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.notification_index + else: + raise NotImplementedError( + f"Cannot retrieve notification_index using RID version {self.rid_version}" + ) + + @property + def owner(self) -> str: + if self.rid_version == RIDVersion.f3411_19: + return self.v19_value.owner + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.owner + else: + raise NotImplementedError( + f"Cannot retrieve owner using RID version {self.rid_version}" + ) + class RIDQuery(ImplicitDict): v19_query: Optional[Query] = None diff --git a/monitoring/prober/infrastructure.py b/monitoring/prober/infrastructure.py index 9647a45a4c..f90115ff5b 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: 370 +# Next code: 372 def register_resource_type(code: int, description: str) -> ResourceType: """Register that the specified code refers to the described resource. diff --git a/monitoring/uss_qualifier/action_generators/documentation/definitions.py b/monitoring/uss_qualifier/action_generators/documentation/definitions.py index 33bb951e80..7557421ef4 100644 --- a/monitoring/uss_qualifier/action_generators/documentation/definitions.py +++ b/monitoring/uss_qualifier/action_generators/documentation/definitions.py @@ -4,7 +4,11 @@ from monitoring.uss_qualifier.action_generators.definitions import GeneratorTypeName from monitoring.uss_qualifier.fileio import FileReference from monitoring.uss_qualifier.scenarios.definitions import TestScenarioTypeName -from monitoring.uss_qualifier.suites.definitions import ActionType, TestSuiteDefinition +from monitoring.uss_qualifier.suites.definitions import ( + ActionType, + TestSuiteDefinition, + TestSuiteTypeName, +) class PotentialTestScenarioAction(ImplicitDict): @@ -13,7 +17,7 @@ class PotentialTestScenarioAction(ImplicitDict): class PotentialTestSuiteAction(ImplicitDict): - suite_type: Optional[FileReference] + suite_type: Optional[TestSuiteTypeName] """Type/location of test suite. Usually expressed as the file name of the suite definition (without extension) qualified relative to the `uss_qualifier` folder""" suite_definition: Optional[TestSuiteDefinition] diff --git a/monitoring/uss_qualifier/common_data_definitions.py b/monitoring/uss_qualifier/common_data_definitions.py index 767c1ffc72..18686c37a2 100644 --- a/monitoring/uss_qualifier/common_data_definitions.py +++ b/monitoring/uss_qualifier/common_data_definitions.py @@ -4,7 +4,7 @@ class Severity(str, Enum): Critical = "Critical" """The system under test has a critical problem that justifies the discontinuation of testing. - + This kind of issue not only makes the current test scenario unable to succeed, but is likely to cause spurious failures in other separate test scenarios as well. This may occur, for instance, if the system was left @@ -15,7 +15,7 @@ class Severity(str, Enum): High = "High" """The system under test has a problem that prevents the current test scenario from continuing. - + Error interrupts a test scenario but likely doesn't impact other, separate test scenarios. For instance, the test step necessary to enable later test steps in the test scenario did not complete successfully. @@ -23,13 +23,65 @@ class Severity(str, Enum): Medium = "Medium" """The system does not meet requirements, but the current test scenario can continue. - + Further test steps will likely still result in reasonable evaluations. """ Low = "Low" """The system meets requirements but could be improved. - + Further test steps can be executed without impact. A test run with only Low-Severity issues will be considered successful. """ + + def __eq__(self, other): + if isinstance(other, Severity): + other_str = other.value + elif isinstance(other, str): + other_str = other + else: + raise ValueError(f"Cannot compare Severity to {type(other)}") + return self.value == other_str + + def __ne__(self, other): + return not (self == other) + + def __gt__(self, other): + if isinstance(other, Severity): + pass + elif isinstance(other, str): + other = Severity(other) + else: + raise ValueError(f"Cannot compare Severity to {type(other)}") + + if self == Severity.Critical: + return other != Severity.Critical + elif self == Severity.High: + return other == Severity.Medium or other == Severity.Low + elif self == Severity.Medium: + return other == Severity.Low + elif self == Severity.Low: + return False + else: + raise ValueError(f"Unknown Severity type: '{self}'") + + def __ge__(self, other): + return self == other or self > other + + def __lt__(self, other): + if isinstance(other, Severity): + pass + elif isinstance(other, str): + other = Severity(other) + else: + raise ValueError(f"Cannot compare Severity to {type(other)}") + return other > self + + def __le__(self, other): + if isinstance(other, Severity): + pass + elif isinstance(other, str): + other = Severity(other) + else: + raise ValueError(f"Cannot compare Severity to {type(other)}") + return other >= self diff --git a/monitoring/uss_qualifier/configurations/README.md b/monitoring/uss_qualifier/configurations/README.md index f052adf98d..a0830a3602 100644 --- a/monitoring/uss_qualifier/configurations/README.md +++ b/monitoring/uss_qualifier/configurations/README.md @@ -2,7 +2,7 @@ ## Usage -To execute a test run with uss_qualifier, a uss_qualifier configuration must be provided. This configuration consists of the test suite to run, along with definitions for all resources needed by that test suite, plus information about artifacts that should be generated. See [`USSQualifierConfiguration`](configuration.py) for the exact schema. +To execute a test run with uss_qualifier, a uss_qualifier configuration must be provided. This configuration consists of the test suite to run, along with definitions for all resources needed by that test suite, plus information about artifacts that should be generated. See [`USSQualifierConfiguration`](configuration.py) for the exact schema and [the dev configurations](./dev) for examples. ### Specifying @@ -67,10 +67,141 @@ Loading _q.json_ results in the object: More details may be found in [`fileio.py`](../fileio.py). +## Execution control + +To skip or selectively execute portions of a test run defined by a configuration, populate [the `execution` field of the `TestConfiguration`](configuration.py). This field controls execution of portions of the test run by skipping actions according to specified criteria. When debugging, this feature can be used to selectively execute only a scenario (or set of scenarios) of interest, or exclude a problematic scenario (or set of scenarios) from execution. Some examples are shown below: + +### Skip all test scenarios: + +_Shows test suite / action generator structure_ + +```yaml +execution: + skip_action_when: + - is_test_scenario: {} +``` + +### Skip a particular test suite + +```yaml +execution: + skip_action_when: + - is_test_suite: + types: [suites.astm.netrid.f3411_22a] +``` + +### Only run two kinds of scenarios + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - is_test_scenario: + types: [scenarios.interuss.mock_uss.configure_locality.ConfigureLocality, scenarios.astm.utm.FlightIntentValidation] +``` + +### Only run the first, ninth, and tenth test scenarios in the test run + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - nth_instance: + n: [{i: 1}, {lo: 9, hi: 10}] + where_action: + is_test_scenario: {} +``` + +### Only run test scenarios with a matching name + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - is_test_scenario: {} + regex_matches_name: 'ASTM NetRID DSS: Simple ISA' +``` + +### Run everything except two kinds of test suites + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + except_when: + - regex_matches_name: 'ASTM F3411-22a' + - is_test_suite: + types: [suites.astm.utm.f3548_21] + - is_test_scenario: {} +``` + +### Only run the immediate test scenario children of a particular test suite + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - is_test_scenario: + has_ancestor: + of_generation: 1 + which: + - is_test_suite: {} + regex_matches_name: 'DSS testing for ASTM NetRID F3548-21' +``` + +### Only run test scenarios that are descendants of a particular test suite + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - is_test_scenario: + has_ancestor: + which: + - is_test_suite: + types: [suites.astm.utm.f3548_21] +``` + +### Only run the third instance of a particular test scenario name + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - nth_instance: + n: [{i: 3}] + where_action: + regex_matches_name: 'Nominal planning: conflict with higher priority' +``` + +### Only run the test scenarios for the second instance of a particular named action generator + +```yaml +execution: + include_action_when: + - is_action_generator: {} + - is_test_suite: {} + - is_test_scenario: {} + has_ancestor: + which: + - nth_instance: + n: [{i: 2}] + where_action: + is_action_generator: {} + regex_matches_name: 'For each appropriate combination of flight planner\(s\)' +``` + ## Design notes 1. Even though all the scenarios, cases, steps and checks are fully defined for a particular test suite, the scenarios require data customized for a particular ecosystem – this data is provided as "test resources" which are created from the specifications in a "test configuration". -2. A test configuration is associated with exactly one test suite, and contains descriptions for how to create each of the set of required test resources. +2. A test configuration is associated with exactly one test action (test scenario, test suite, action generator), and contains descriptions for how to create each of the set of required test resources. * The resources required for a particular test definition depend on which test scenarios are included in the test suite. 3. One resource can be used by many different test scenarios. 4. One test scenario may use multiple resources. diff --git a/monitoring/uss_qualifier/configurations/configuration.py b/monitoring/uss_qualifier/configurations/configuration.py index cd6526cd2f..94db32259b 100644 --- a/monitoring/uss_qualifier/configurations/configuration.py +++ b/monitoring/uss_qualifier/configurations/configuration.py @@ -1,21 +1,132 @@ +from __future__ import annotations from typing import Optional, List, Dict from implicitdict import ImplicitDict from monitoring.monitorlib.dicts import JSONAddress +from monitoring.uss_qualifier.action_generators.definitions import GeneratorTypeName from monitoring.uss_qualifier.reports.validation.definitions import ( ValidationConfiguration, ) from monitoring.uss_qualifier.requirements.definitions import RequirementCollection from monitoring.uss_qualifier.resources.definitions import ResourceCollection +from monitoring.uss_qualifier.scenarios.definitions import TestScenarioTypeName from monitoring.uss_qualifier.suites.definitions import ( TestSuiteActionDeclaration, + TestSuiteTypeName, ) ParticipantID = str """String that refers to a participant being qualified by uss_qualifier""" +class InstanceIndexRange(ImplicitDict): + lo: Optional[int] + """If specified, no indices lower than this value will be included in the range.""" + + i: Optional[int] + """If specified, no index other than this one will be included in the range.""" + + hi: Optional[int] + """If specified, no indices higher than this value will be included in the range.""" + + def includes(self, i: int) -> bool: + if "i" in self and self.i is not None and i != self.i: + return False + if "lo" in self and self.lo is not None and i < self.lo: + return False + if "hi" in self and self.hi is not None and i > self.hi: + return False + return True + + +class ActionGeneratorSelectionCondition(ImplicitDict): + """By default, select all action generators. When specified, limit selection to specified conditions.""" + + types: Optional[List[GeneratorTypeName]] + """Only select action generators of the specified types.""" + + +class TestSuiteSelectionCondition(ImplicitDict): + """By default, select all test suites. When specified, limit selection to specified conditions.""" + + types: Optional[List[TestSuiteTypeName]] + """Only select test suites of the specified types.""" + + +class TestScenarioSelectionCondition(ImplicitDict): + """By default, select all test scenarios. When specified, limit selection to specified conditions.""" + + types: Optional[List[TestScenarioTypeName]] + """Only select test scenarios of the specified types.""" + + +class NthInstanceCondition(ImplicitDict): + """Select an action once a certain number of matching instances have happened.""" + + n: List[InstanceIndexRange] + """Only select an action if it is one of these nth instances.""" + + where_action: TestSuiteActionSelectionCondition + """Condition that an action must meet to be selected as an instance in this condition.""" + + +class AncestorSelectionCondition(ImplicitDict): + """Select ancestor actions meeting all the specified conditions.""" + + of_generation: Optional[int] + """The ancestor is exactly this many generations removed (1 = parent, 2 = grandparent, etc). + + If not specified, an ancestor of any generation meeting the `which` conditions will be selected.""" + + which: List[TestSuiteActionSelectionCondition] + """Only select an ancestor meeting ALL of these conditions.""" + + +class TestSuiteActionSelectionCondition(ImplicitDict): + """Condition for selecting TestSuiteActions. + + If more than one subcondition is specified, satisfaction of ALL subconditions are necessary to select the action.""" + + is_action_generator: Optional[ActionGeneratorSelectionCondition] + """Select these action generator actions.""" + + is_test_suite: Optional[TestSuiteSelectionCondition] + """Select these test suite actions.""" + + is_test_scenario: Optional[TestScenarioSelectionCondition] + """Select these test scenario actions.""" + + regex_matches_name: Optional[str] + """Select actions where this regular expression has a match in the action's name.""" + + defined_at: Optional[List[JSONAddress]] + """Select actions defined at one of the specified addresses. + + The top-level action in a test run is 'test_scenario', 'test_suite', or 'action_generator'. Children use the + 'actions' property, but then must specify the type of the action. So, e.g., the test scenario that is the third + action of a test suite which is the second action in an action generator would be + 'action_generator.actions[1].test_suite.actions[2].test_scenario'. An address that starts or ends with 'actions[i]' + is invalid and will never match.""" + + nth_instance: Optional[NthInstanceCondition] + """Select only certain instances of matching actions.""" + + has_ancestor: Optional[AncestorSelectionCondition] + """Select only actions with a matching ancestor.""" + + except_when: Optional[List[TestSuiteActionSelectionCondition]] + """Do not select actions selected by any of these conditions, even when they are selected by one or more conditions above.""" + + +class ExecutionConfiguration(ImplicitDict): + include_action_when: Optional[List[TestSuiteActionSelectionCondition]] = None + """If specified, only execute test actions if they are selected by ANY of these conditions (and not selected by any of the `skip_when` conditions).""" + + skip_action_when: Optional[List[TestSuiteActionSelectionCondition]] = None + """If specified, do not execute test actions if they are selected by ANY of these conditions.""" + + class TestConfiguration(ImplicitDict): action: TestSuiteActionDeclaration """The action this test configuration wants to run (usually a test suite)""" @@ -26,6 +137,9 @@ class TestConfiguration(ImplicitDict): resources: ResourceCollection """Declarations for resources used by the test suite""" + execution: Optional[ExecutionConfiguration] + """Specification for how to execute the test run.""" + TestedRequirementsCollectionIdentifier = str """Identifier for a requirements collection, local to a TestedRequirementsConfiguration artifact configuration.""" diff --git a/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml b/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml index af807b8ab7..a6c1c6394b 100644 --- a/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml +++ b/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml @@ -41,6 +41,4 @@ v1: uss1: all_astm_dss_requirements uss2: all_astm_dss_requirements validation: - criteria: - - full_success: {} - - no_skipped_actions: {} + $ref: ./library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/f3548/flight_intent_validation.yaml b/monitoring/uss_qualifier/configurations/dev/f3548/flight_intent_validation.yaml deleted file mode 100644 index acd0084997..0000000000 --- a/monitoring/uss_qualifier/configurations/dev/f3548/flight_intent_validation.yaml +++ /dev/null @@ -1,25 +0,0 @@ -v1: - test_run: - resources: - resource_declarations: - che_invalid_flight_intents: {$ref: '../library/resources.yaml#/che_invalid_flight_intents'} - - utm_auth: {$ref: '../library/environment.yaml#/utm_auth'} - uss1_flight_planner: {$ref: '../library/environment.yaml#/uss1_flight_planner'} - scd_dss: {$ref: '../library/environment.yaml#/scd_dss'} - non_baseline_inputs: - - v1.test_run.resources.resource_declarations.utm_auth - - v1.test_run.resources.resource_declarations.uss1_flight_planner - - v1.test_run.resources.resource_declarations.scd_dss - action: - test_scenario: - scenario_type: scenarios.astm.utm.FlightIntentValidation - resources: - tested_uss: uss1_flight_planner - flight_intents: che_invalid_flight_intents - dss: scd_dss - artifacts: - report: - report_path: output/report_f3548_flight_intent_validation.json - validation: - $ref: ../library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/f3548/nominal_planning_conflict_equal_priority_not_permitted.yaml b/monitoring/uss_qualifier/configurations/dev/f3548/nominal_planning_conflict_equal_priority_not_permitted.yaml deleted file mode 100644 index 29dbc20501..0000000000 --- a/monitoring/uss_qualifier/configurations/dev/f3548/nominal_planning_conflict_equal_priority_not_permitted.yaml +++ /dev/null @@ -1,28 +0,0 @@ -v1: - test_run: - resources: - resource_declarations: - che_conflicting_flights: {$ref: '../library/resources.yaml#/che_conflicting_flights'} - - utm_auth: {$ref: '../library/environment.yaml#/utm_auth'} - uss1_flight_planner: {$ref: '../library/environment.yaml#/uss1_flight_planner'} - uss2_flight_planner: {$ref: '../library/environment.yaml#/uss2_flight_planner'} - scd_dss: {$ref: '../library/environment.yaml#/scd_dss'} - non_baseline_inputs: - - v1.test_run.resources.resource_declarations.utm_auth - - v1.test_run.resources.resource_declarations.uss1_flight_planner - - v1.test_run.resources.resource_declarations.uss1_flight_planner - - v1.test_run.resources.resource_declarations.scd_dss - action: - test_scenario: - scenario_type: scenarios.astm.utm.ConflictEqualPriorityNotPermitted - resources: - tested_uss: uss1_flight_planner - control_uss: uss2_flight_planner - flight_intents: che_conflicting_flights - dss: scd_dss - artifacts: - report: - report_path: output/report_f3548_nominal_planning_conflict_equal_priority_not_permitted.json - validation: - $ref: ../library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/f3548/nominal_planning_conflict_higher_priority.yaml b/monitoring/uss_qualifier/configurations/dev/f3548/nominal_planning_conflict_higher_priority.yaml deleted file mode 100644 index 545ff29535..0000000000 --- a/monitoring/uss_qualifier/configurations/dev/f3548/nominal_planning_conflict_higher_priority.yaml +++ /dev/null @@ -1,28 +0,0 @@ -v1: - test_run: - resources: - resource_declarations: - che_conflicting_flights: {$ref: '../library/resources.yaml#/che_conflicting_flights'} - - utm_auth: {$ref: '../library/environment.yaml#/utm_auth'} - uss1_flight_planner: {$ref: '../library/environment.yaml#/uss1_flight_planner'} - uss2_flight_planner: {$ref: '../library/environment.yaml#/uss2_flight_planner'} - scd_dss: {$ref: '../library/environment.yaml#/scd_dss'} - non_baseline_inputs: - - v1.test_run.resources.resource_declarations.utm_auth - - v1.test_run.resources.resource_declarations.uss1_flight_planner - - v1.test_run.resources.resource_declarations.uss1_flight_planner - - v1.test_run.resources.resource_declarations.scd_dss - action: - test_scenario: - scenario_type: scenarios.astm.utm.ConflictHigherPriority - resources: - tested_uss: uss1_flight_planner - control_uss: uss2_flight_planner - flight_intents: che_conflicting_flights - dss: scd_dss - artifacts: - report: - report_path: output/report_f3548_nominal_planning_conflict_higher_priority.json - validation: - $ref: ../library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml index e2f6665e06..bec2b8c036 100644 --- a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml +++ b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml @@ -113,3 +113,21 @@ v1: # Write out a human-readable report showing the sequence of events of the test sequence_view: {} + + # This block defines whether to return an error code from the execution of uss_qualifier, based on the content of the + # test run report. All of the criteria must be met to return a successful code. + validation: + criteria: + # applicability indicates which test report elements the pass_condition applies to + - applicability: + # We want to make sure there are no failed checks... + failed_checks: + # ...at least, no failed checks with severity higher than "Low". + has_severity: + higher_than: Low + pass_condition: + # When considering all of the applicable elements... + elements: + # ...the number of applicable elements should be zero. + count: + equal_to: 0 diff --git a/monitoring/uss_qualifier/configurations/dev/general_flight_auth.yaml b/monitoring/uss_qualifier/configurations/dev/general_flight_auth.yaml index eda18dbd56..e1d39e5280 100644 --- a/monitoring/uss_qualifier/configurations/dev/general_flight_auth.yaml +++ b/monitoring/uss_qualifier/configurations/dev/general_flight_auth.yaml @@ -4,13 +4,34 @@ v1: resources: resource_declarations: example_flight_check_table: {$ref: 'library/resources.yaml#/example_flight_check_table'} + + utm_auth: {$ref: 'library/environment.yaml#/utm_auth'} + uss1_flight_planner: {$ref: 'library/environment.yaml#/uss1_flight_planner'} + non_baseline_inputs: + - v1.test_run.resources.resource_declarations.utm_auth + - v1.test_run.resources.resource_declarations.uss1_flight_planner action: test_scenario: scenario_type: scenarios.interuss.flight_authorization.GeneralFlightAuthorization resources: table: example_flight_check_table + planner: uss1_flight_planner artifacts: output_path: output/general_flight_auth raw_report: {} + sequence_view: {} + tested_requirements: + - report_name: requirements + requirement_collections: + example: + requirement_collections: + - requirements: + - REQ_001 + - REQ_002 + - REQ_003 + - REQ_004 + - REQ_007 + participant_requirements: + uss1: example validation: $ref: ./library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/library/resources.yaml b/monitoring/uss_qualifier/configurations/dev/library/resources.yaml index b3667c5a28..0fee0615c5 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/resources.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/resources.yaml @@ -151,73 +151,113 @@ example_flight_check_table: - REQ_002 - REQ_007 description: The first test step defined by the test designer - additional_information: - new_jurisdiction_x: - operation_rule_set: Rules1 - volumes: - - outline_circle: - center: - lng: 7.4774 - lat: 46.9749 - radius: - value: 100 - units: M - altitude_lower: - value: 0 - units: M - reference: SFC - altitude_upper: - value: 100 - units: M - reference: SFC - start_time: - start_of_test: { } - use_timezone: Europe/Berlin - end_time: - offset_from: - starting_from: - next_day: - time_zone: Europe/Zurich - starting_from: - start_of_test: { } - days_of_the_week: [ "Tu", "Th" ] - offset: 12h acceptance_expectation: MustBeAccepted + flight_info: + basic_information: + usage_state: Planned + uas_state: Nominal + area: + - outline_circle: + center: + lng: 7.4774 + lat: 46.9749 + radius: + value: 100 + units: M + altitude_lower: + value: 550 + units: M + # TODO: Change to SFC once mock_uss can process that datum + reference: W84 + altitude_upper: + value: 650 + units: M + # TODO: Change to SFC once mock_uss can process that datum + reference: W84 + start_time: + start_of_test: { } + use_timezone: Europe/Berlin + end_time: + offset_from: + starting_from: + next_day: + time_zone: Europe/Zurich + starting_from: + start_of_test: { } + days_of_the_week: [ "Tu", "Th" ] + offset: 12h + additional_information: + new_jurisdiction_x: + operation_rule_set: Rules1 + # TODO: Remove once mock_uss is fixed to not require U-space flight auth + uspace_flight_authorisation: + uas_serial_number: 1AF49UL5CC5J6K + operation_category: Open + operation_mode: Vlos + uas_class: C0 + identification_technologies: + - ASTMNetRID + connectivity_methods: + - cellular + endurance_minutes: 30 + emergency_procedure_url: https://example.interussplatform.org/emergency + operator_id: CHEo5kut30e0mt01-qwe + uas_id: '' + uas_type_certificate: '' - flight_check_id: TEST_002 requirement_ids: - REQ_001 - REQ_003 - REQ_004 description: The second test step defined by the test designer - additional_information: - new_jurisdiction_x: - operation_rule_set: Rules1 - volumes: - - outline_circle: - center: - lng: 7.4774 - lat: 46.9749 - radius: - value: 100 - units: M - altitude_lower: - value: 50 - units: M - reference: SFC - altitude_upper: - value: 5000 - units: FT - reference: W84 - start_time: - next_day: - time_zone: +02:00 - starting_from: - offset_from: + acceptance_expectation: MustBeAccepted + flight_info: + basic_information: + usage_state: Planned + uas_state: Nominal + area: + - outline_circle: + center: + lng: 7.4774 + lat: 46.9749 + radius: + value: 100 + units: M + altitude_lower: + value: 1424 + units: M + reference: W84 + altitude_upper: + value: 5000 + units: FT + reference: W84 + start_time: + next_day: + time_zone: +02:00 starting_from: - start_of_test: { } - offset: 12h - duration: 5m - conditions_expectation: MustBePresent + offset_from: + starting_from: + start_of_test: { } + offset: 12h + duration: 5m + additional_information: + new_jurisdiction_x: + operation_rule_set: Rules1 + # TODO: Remove once mock_uss is fixed to not require U-space flight auth + uspace_flight_authorisation: + uas_serial_number: 1AF49UL5CC5J6K + operation_category: Open + operation_mode: Vlos + uas_class: C0 + identification_technologies: + - ASTMNetRID + connectivity_methods: + - cellular + endurance_minutes: 30 + emergency_procedure_url: https://example.interussplatform.org/emergency + operator_id: CHEo5kut30e0mt01-qwe + uas_id: '' + uas_type_certificate: '' # ===== Geospatial feature comprehension ===== diff --git a/monitoring/uss_qualifier/configurations/dev/library/validation.yaml b/monitoring/uss_qualifier/configurations/dev/library/validation.yaml index b049c2247c..16db8582c3 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/validation.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/validation.yaml @@ -1,5 +1,11 @@ normal_test: $content_schema: monitoring/uss_qualifier/reports/validation/report_validation/ValidationConfiguration.json criteria: - - full_success: {} - - no_skipped_actions: {} + - applicability: + failed_checks: + has_severity: + higher_than: Low + pass_condition: + elements: + count: + equal_to: 0 diff --git a/monitoring/uss_qualifier/main.py b/monitoring/uss_qualifier/main.py index 8351de8b2c..e820b1ecb4 100644 --- a/monitoring/uss_qualifier/main.py +++ b/monitoring/uss_qualifier/main.py @@ -33,7 +33,7 @@ compute_signature, compute_baseline_signature, ) -from monitoring.uss_qualifier.suites.suite import TestSuiteAction +from monitoring.uss_qualifier.suites.suite import TestSuiteAction, ExecutionContext from monitoring.uss_qualifier.validation import validate_config @@ -97,9 +97,10 @@ def execute_test_run(whole_config: USSQualifierConfiguration): environment_signature = compute_signature(environment) logger.info("Instantiating top-level test suite action") + context = ExecutionContext(config.execution if "execution" in config else None) action = TestSuiteAction(config.action, resources) logger.info("Running top-level test suite action") - report = action.run() + report = action.run(context) if report.successful(): logger.info("Final result: SUCCESS") else: diff --git a/monitoring/uss_qualifier/reports/report.py b/monitoring/uss_qualifier/reports/report.py index 3f3eebbe41..7be295136b 100644 --- a/monitoring/uss_qualifier/reports/report.py +++ b/monitoring/uss_qualifier/reports/report.py @@ -273,7 +273,7 @@ def query_passed_checks( def query_failed_checks( self, participant_id: Optional[str] = None - ) -> Iterator[Tuple[JSONPathExpression, PassedCheck]]: + ) -> Iterator[Tuple[JSONPathExpression, FailedCheck]]: for i, case in enumerate(self.cases): for path, fc in case.query_failed_checks(participant_id): yield f"cases[{i}].{path}", fc @@ -457,7 +457,7 @@ def query_passed_checks( def query_failed_checks( self, participant_id: Optional[str] = None - ) -> Iterator[Tuple[JSONPathExpression, PassedCheck]]: + ) -> Iterator[Tuple[JSONPathExpression, FailedCheck]]: test_suite, test_scenario, action_generator = self.get_applicable_report() if test_suite: report = self.test_suite @@ -486,7 +486,9 @@ def start_time(self) -> Optional[StringBasedDateTime]: @property def end_time(self) -> Optional[StringBasedDateTime]: - return self._conditional(lambda report: report.end_time) + return self._conditional( + lambda report: report.end_time if "end_time" in report else None + ) class AllConditionsEvaluationReport(ImplicitDict): @@ -626,9 +628,6 @@ class SkippedActionReport(ImplicitDict): reason: str """The reason the action was skipped.""" - action_declaration_index: int - """Index of the skipped action in the configured declaration.""" - declaration: TestSuiteActionDeclaration """Full declaration of the action that was skipped.""" diff --git a/monitoring/uss_qualifier/reports/sequence_view.py b/monitoring/uss_qualifier/reports/sequence_view.py index eb2f4e7a77..356f771b84 100644 --- a/monitoring/uss_qualifier/reports/sequence_view.py +++ b/monitoring/uss_qualifier/reports/sequence_view.py @@ -404,7 +404,10 @@ def append_notes(new_notes): if "end_time" in report and report.end_time: latest_step_time = report.end_time.datetime - dt_s = round((latest_step_time - report.start_time.datetime).total_seconds()) + if latest_step_time is not None: + dt_s = round((latest_step_time - report.start_time.datetime).total_seconds()) + else: + dt_s = 0 dt_m = math.floor(dt_s / 60) dt_s -= dt_m * 60 padding = "0" if dt_s < 10 else "" @@ -448,7 +451,7 @@ def _skipped_action_of(report: SkippedActionReport) -> ActionNode: raise ValueError( f"Cannot process skipped action for test suite that does not define suite_type nor suite_definition" ) - name = "All scenarios in test suite" + name = "All actions in test suite" elif report.declaration.get_action_type() == ActionType.TestScenario: docs = get_documentation_by_name(report.declaration.test_scenario.scenario_type) return ActionNode( @@ -466,7 +469,7 @@ def _skipped_action_of(report: SkippedActionReport) -> ActionNode: node_type=ActionNodeType.ActionGenerator, children=[], ) - name = f"All scenarios from action generator" + name = f"All actions from action generator" else: raise ValueError( f"Cannot process skipped action of type '{report.declaration.get_action_type()}'" diff --git a/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html b/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html index 4595384515..97c3bad31a 100644 --- a/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html +++ b/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html @@ -46,6 +46,10 @@ h2 { margin-block-end: 0.1em; } + p { + margin-top: 0.1em; + margin-bottom: 0.1em; + } .sticky_cell_value { position: sticky; top: 40px; diff --git a/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html b/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html index 782fa355b3..26b245cf9e 100644 --- a/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html +++ b/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html @@ -2,7 +2,7 @@