diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 1b070c6e24..71e4a247ef 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -7773,14 +7773,6 @@ "lineCount": 1 } }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 46, - "endColumn": 64, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -7805,14 +7797,6 @@ "lineCount": 1 } }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 30, - "lineCount": 1 - } - }, { "code": "reportArgumentType", "range": { @@ -7829,14 +7813,6 @@ "lineCount": 1 } }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 49, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -7844,22 +7820,6 @@ "endColumn": 72, "lineCount": 1 } - }, - { - "code": "reportInvalidTypeForm", - "range": { - "startColumn": 26, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 46, - "lineCount": 1 - } } ], "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py": [ @@ -16116,786 +16076,338 @@ { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 16, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 63, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 67, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 54, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 33, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 54, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 33, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 41, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 27, + "endColumn": 28, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 29, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, + "startColumn": 50, "endColumn": 57, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 27, + "endColumn": 29, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 33, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 42, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 63, - "endColumn": 71, + "startColumn": 27, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 67, - "endColumn": 75, + "startColumn": 29, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 50, + "endColumn": 57, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 34, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 21, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 17, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 56, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 59, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 37, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 33, + "endColumn": 44, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 24, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 19, + "endColumn": 20, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 97, + "endColumn": 98, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 42, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 54, - "endColumn": 61, + "startColumn": 20, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 33, - "endColumn": 53, + "startColumn": 18, + "endColumn": 19, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 59, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 54, - "endColumn": 61, + "startColumn": 28, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 50, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 41, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 27, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 50, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 27, - "endColumn": 29, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 50, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 27, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 50, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 34, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 21, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 17, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 56, - "endColumn": 67, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 40, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 59, - "endColumn": 87, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 50, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 40, - "endColumn": 47, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 97, - "endColumn": 98, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 42, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 20, - "endColumn": 23, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 18, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 59, - "endColumn": 87, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 28, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 42, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 23, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 18, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 20, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 18, + "endColumn": 19, "lineCount": 1 } }, @@ -16923,134 +16435,6 @@ "lineCount": 1 } }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 57, - "lineCount": 1 - } - }, { "code": "reportOptionalOperand", "range": { diff --git a/NEXT_RELEASE_NOTES.md b/NEXT_RELEASE_NOTES.md index c5df1b32bc..9951f6b0fc 100644 --- a/NEXT_RELEASE_NOTES.md +++ b/NEXT_RELEASE_NOTES.md @@ -37,6 +37,8 @@ The release notes should contain at least the following sections: ## Mandatory migration tasks +The field `astm_url_regexes` in the USSIdentificationResource has been changed to `server_url_regexes`. Any test configurations using this resource (likely NetRID configurations) must change `astm_url_regexes` to `server_url_regexes`. + ## Optional migration tasks ## Important information diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml index d99bbcb946..d2f5b4015e 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml @@ -312,11 +312,11 @@ uss_identification: specification: uss_identifiers: uss1: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*uss1\.localutm.*' uss2: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*uss2\.localutm.*' uss3: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*uss3\.localutm.*' diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml index e8da109249..1820cf11f3 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml @@ -279,8 +279,8 @@ uss_identification: specification: uss_identifiers: uss1: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*localhost:8074.*' uss2: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*localhost:8094.*' diff --git a/monitoring/uss_qualifier/resources/interuss/uss_identification.py b/monitoring/uss_qualifier/resources/interuss/uss_identification.py index b6958280e7..c35fa1110c 100644 --- a/monitoring/uss_qualifier/resources/interuss/uss_identification.py +++ b/monitoring/uss_qualifier/resources/interuss/uss_identification.py @@ -1,8 +1,9 @@ import re from implicitdict import ImplicitDict, Optional +from loguru import logger -from monitoring.monitorlib.fetch import Query +from monitoring.monitorlib.fetch import Query, QueryType from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.resource import Resource @@ -16,12 +17,19 @@ class AccessTokenIdentifier(ImplicitDict): class USSIdentifiers(ImplicitDict): - astm_url_regexes: Optional[list[str]] - """If a URL to an ASTM (F3411, F3548, etc) endpoint matches one of these regular expressions, assume the participant is responsible for that server""" + server_url_regexes: Optional[list[str]] + """If a URL to an endpoint matches one of these regular expressions, assume the participant is responsible for that server""" access_tokens: Optional[list[AccessTokenIdentifier]] """If an access token matches one of these identifiers, assume the participant is responsible for that access token""" + def matches_server_url(self, url: str) -> bool: + if "server_url_regexes" in self and self.server_url_regexes: + for url_regex in self.server_url_regexes: + if re.fullmatch(url_regex, url): + return True + return False + class USSIdentificationSpecification(ImplicitDict): uss_identifiers: dict[ParticipantID, USSIdentifiers] @@ -40,7 +48,8 @@ def __init__( super().__init__(specification, resource_origin) self.identifiers = specification.uss_identifiers or {} - def attribute_query(self, query: Query) -> None: + def attribute_query_server(self, query: Query) -> None: + """Identify the participant ID of the server responding to this query and mutate `query` accordingly, if possible""" claims = query.request.token if "error" in claims and len(claims) == 1: claims = None @@ -48,10 +57,58 @@ def attribute_query(self, query: Query) -> None: for participant_id, identifiers in self.identifiers.items(): attribute_to_participant = False - if "astm_url_regexes" in identifiers and identifiers.astm_url_regexes: - for url_regex in identifiers.astm_url_regexes: - if re.fullmatch(url_regex, query.request.url): - attribute_to_participant = True + if "server_url_regexes" in identifiers and identifiers.server_url_regexes: + # The participant is responsible for the server end of the query when the query URL matches one of the participant's + if identifiers.matches_server_url(query.request.url): + attribute_to_participant = True + + if attribute_to_participant: + query.participant_id = participant_id + + def identify_query_client(self, query: Query) -> ParticipantID | None: + """Identify the participant ID of the client making this query if possible""" + claims = query.request.token + if "error" in claims and len(claims) == 1: + claims = None + + query_type = query.query_type if "query_type" in query else None + if query_type == QueryType.F3411v22aUSSPostIdentificationServiceArea: + url = ( + (query.request.json or {}) + .get("service_area", {}) + .get("uss_base_url", None) + ) + elif query_type == QueryType.F3411v19USSPostIdentificationServiceArea: + url = ( + (query.request.json or {}) + .get("service_area", {}) + .get("flights_url", None) + ) + elif query_type == QueryType.F3548v21USSNotifyOperationalIntentDetailsChanged: + url = ( + (query.request.json or {}) + .get("operational_intent", {}) + .get("reference", {}) + .get("uss_base_url", None) + ) + elif query_type == QueryType.F3548v21USSNotifyConstraintDetailsChanged: + url = ( + (query.request.json or {}) + .get("constraint", {}) + .get("reference", {}) + .get("uss_base_url", None) + ) + else: + url = None + + matching_participants = [] + for participant_id, identifiers in self.identifiers.items(): + attribute_to_participant = False + + if "server_url_regexes" in identifiers and identifiers.server_url_regexes: + # The participant is responsible for the client end of the query when the request body is a payload specifying a callback server operated by the participant + if url and identifiers.matches_server_url(url): + attribute_to_participant = True if "access_tokens" in identifiers and identifiers.access_tokens: for access_token_identifier in identifiers.access_tokens: @@ -70,4 +127,15 @@ def attribute_query(self, query: Query) -> None: attribute_to_participant = True if attribute_to_participant: - query.participant_id = participant_id + matching_participants.append(participant_id) + + if len(matching_participants) == 1: + return matching_participants[0] + elif len(matching_participants) > 1: + logger.warning( + "Multiple participants {} match as clients for query {} {}", + ", ".join(matching_participants), + query.request.method, + query.request.url, + ) + return None diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py index 139dbb4485..032b1151c1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py @@ -182,7 +182,7 @@ def _step_create_isa(self): if self._identification is not None: # Attribute notifications to participants when possible for base_url, notification in isa_change.notifications.items(): - self._identification.attribute_query(notification.query) + self._identification.attribute_query_server(notification.query) # For any attributed notifications, check that the recipient acknowledged them correctly for base_url, notification in isa_change.notifications.items(): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py index 2fb117675d..d035b30bc8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py @@ -1,28 +1,28 @@ from collections.abc import Callable -from datetime import timedelta +from datetime import datetime, timedelta from typing import TypeVar -from future.backports.datetime import datetime -from implicitdict import ImplicitDict -from requests.exceptions import RequestException +import arrow from s2sphere import LatLngRect -from uas_standards.astm.f3411.v19 import api as api_v19 -from uas_standards.astm.f3411.v22a import api as api_v22a from monitoring.monitorlib.clients.mock_uss.interactions import ( Interaction, QueryDirection, ) -from monitoring.monitorlib.errors import stacktrace_string +from monitoring.monitorlib.fetch import Query, QueryType from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.temporal import Time from monitoring.prober.infrastructure import register_resource_type +from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstancesResource from monitoring.uss_qualifier.resources.interuss import IDGeneratorResource from monitoring.uss_qualifier.resources.interuss.mock_uss.client import ( MockUSSClient, MockUSSResource, ) +from monitoring.uss_qualifier.resources.interuss.uss_identification import ( + USSIdentificationResource, +) from monitoring.uss_qualifier.resources.netrid import ( FlightDataResource, NetRIDServiceProviders, @@ -36,7 +36,7 @@ from monitoring.uss_qualifier.scenarios.interuss.mock_uss.test_steps import ( direction_filter, get_mock_uss_interactions, - status_code_filter, + query_type_filter, ) from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext @@ -50,6 +50,7 @@ class ServiceProviderNotificationBehavior(GenericTestScenario): _flights_data: FlightDataResource _service_providers: NetRIDServiceProviders _mock_uss: MockUSSClient + _uss_identification: USSIdentificationResource _injected_flights: list[InjectedFlight] _injected_tests: list[InjectedTest] @@ -64,11 +65,13 @@ def __init__( mock_uss: MockUSSResource, id_generator: IDGeneratorResource, dss_pool: DSSInstancesResource, + uss_identification: USSIdentificationResource, ): super().__init__() self._flights_data = flights_data self._service_providers = service_providers self._mock_uss = mock_uss.mock_uss + self._uss_identification = uss_identification self._dss_wrapper = DSSWrapper(self, dss_pool.dss_instances[0]) self._injected_tests = [] @@ -98,14 +101,12 @@ def run(self, context: ExecutionContext): self.end_test_step() self.begin_test_step("Injection") + injection_time = arrow.utcnow().datetime self._inject_flights() self.end_test_step() self.begin_test_step("Validate Mock USS received notification") - # Given that we know when flights are injected, we could narrow down the time window for - # which we are looking for notifications to something more precise than scenario start time. - # TODO tracked in #1052 - self._validate_mock_uss_notifications(context.start_time) + self._validate_mock_uss_notifications(injection_time) self.end_test_step() self.end_test_case() @@ -154,42 +155,56 @@ def _validate_mock_uss_notifications(self, notifications_received_after: datetim [tf.uss_participant_id for tf in self._injected_flights] ) - def post_isa_filter(interaction: Interaction): - return ( - interaction.query.request.method == "POST" - and "/uss/identification_service_areas/" - in interaction.query.request.url - ) - - def fetch_interactions() -> list[Interaction]: + def due_to_subscription_filter(interaction: Interaction) -> bool: + """Select only interactions that are notifications due to subscription we established""" + subscriptions = [ + sub.get("subscription_id", None) + for sub in (interaction.query.request.json or {}).get( + "subscriptions", [] + ) + ] + if self._subscription_id not in subscriptions: + return False + return True + + def fetch_interactions() -> tuple[list[Interaction], Query]: + if self._rid_version == RIDVersion.f3411_22a: + query_type = QueryType.F3411v22aUSSPostIdentificationServiceArea + elif self._rid_version == RIDVersion.f3411_19: + query_type = QueryType.F3411v19USSPostIdentificationServiceArea + else: + raise NotImplementedError() return get_mock_uss_interactions( self, self._mock_uss, Time(notifications_received_after), direction_filter(QueryDirection.Incoming), - status_code_filter(204), - post_isa_filter, - )[0] + query_type_filter(query_type), + due_to_subscription_filter, + ) - def includes_all_notifications(raw_interactions: list[Interaction]) -> bool: + def includes_all_notifications( + raw_interactions: tuple[list[Interaction], Query], + ) -> bool: pids_having_notified = self._relevant_notified_subs( - raw_interactions, relevant_participant_ids, notifications_received_after + raw_interactions[0], relevant_participant_ids ) # We're done once we have a notification from each SP we injected a flight in return len(pids_having_notified) == len(relevant_participant_ids) # notifications are not immediate: we optimistically try early, and retry until # the permissible delay has passed, or we have received all notifications. - interactions = self._retry_with_backoff( + interactions, query = self._retry_with_backoff( fetch_interactions, retries=3, delay_s=1, delay_reason="waiting for expected notifications to be delivered", was_successful=includes_all_notifications, ) + # fish out the notification times per participant ID notifs_by_participant = self._relevant_notified_subs( - interactions, relevant_participant_ids, notifications_received_after + interactions, relevant_participant_ids ) # For each of the service providers we injected flights in, # check that we received a notification. We don't check the latency as a single datapoint does not allow @@ -208,61 +223,27 @@ def includes_all_notifications(raw_interactions: list[Interaction]) -> bool: check.record_failed( summary="No notification received", details=f"No notification received within roughly {self._rid_version.dp_data_resp_percentile99_s} seconds from {test_flight.uss_participant_id} for subscription {self._subscription_id} about flight {test_flight.test_id} that happened within the subscription's boundaries.", + queries=query, ) continue - def _notif_operation_id(self) -> api_v19.OperationID | api_v22a.OperationID: - if self._rid_version.f3411_19: - return api_v19.OperationID.PostIdentificationServiceArea - elif self._rid_version.f3411_22a: - return api_v22a.OperationID.PostIdentificationServiceArea - else: - raise ValueError(f"Unsupported RID version: {self._rid_version}") - - def _notif_param_type( - self, - ) -> type[ - api_v19.PutIdentificationServiceAreaNotificationParameters - | api_v22a.PutIdentificationServiceAreaNotificationParameters - ]: - if self._rid_version == RIDVersion.f3411_19: - return api_v19.PutIdentificationServiceAreaNotificationParameters - elif self._rid_version == RIDVersion.f3411_22a: - return api_v22a.PutIdentificationServiceAreaNotificationParameters - else: - raise ValueError(f"Unsupported RID version: {self._rid_version}") - def _relevant_notified_subs( self, raw_interactions: list[Interaction], relevant_pids: set[str], - received_after: datetime, ) -> dict[str, list[datetime]]: - # Parse version-specific notification parameters - PutIsaParamsType = self._notif_param_type() - - relevant = [] + notifs_by_participant: dict[ParticipantID, list[datetime]] = {} for interaction in raw_interactions: - received_at = interaction.query.request.received_at.datetime - notification: PutIsaParamsType = ImplicitDict.parse( - interaction.query.request.json, PutIsaParamsType + # Associate queries' clients to participants where possible + participant_id = self._uss_identification.identify_query_client( + interaction.query ) - for sub in notification.subscriptions: - if ( - sub.subscription_id == self._subscription_id - and "service_area" - in notification # deletion notification don't have this field - and notification.service_area.owner in relevant_pids - # We may sometimes receive slightly older and unrelated notifications which we filter out - and received_at > received_after - ): - relevant.append((received_at, notification.service_area.owner)) - - notifs_by_participant: dict[str, list[datetime]] = {} - for received_at, participant_id in relevant: - if participant_id not in notifs_by_participant: - notifs_by_participant[participant_id] = [] - notifs_by_participant[participant_id].append(received_at) + if not participant_id or participant_id not in relevant_pids: + continue + notifs = notifs_by_participant.get(participant_id, []) + notifs.append(interaction.query.request.received_at.datetime) + notifs_by_participant[participant_id] = notifs + return notifs_by_participant def cleanup(self): @@ -285,19 +266,15 @@ def cleanup(self): ) sp = matching_sps[0] check = self.check("Successful test deletion", [sp.participant_id]) - try: - query = sp.delete_test(injected_test.test_id, injected_test.version) - self.record_query(query) - if query.status_code != 200: - raise ValueError( - f"Received status code {query.status_code} after attempting to delete test {injected_test.test_id} at version {injected_test.version} from service provider {sp.participant_id}" - ) + query = sp.delete_test(injected_test.test_id, injected_test.version) + self.record_query(query) + if query.status_code == 200: check.record_passed() - except (RequestException, ValueError) as e: - stacktrace = stacktrace_string(e) + else: check.record_failed( - summary="Error while trying to delete test flight", - details=f"While trying to delete a test flight from {sp.participant_id}, encountered error:\n{stacktrace}", + summary="Test flight deletion was unsuccessful", + details=f"Received status code {query.status_code} after attempting to delete test {injected_test.test_id} at version {injected_test.version} from service provider {sp.participant_id}", + queries=query, ) self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md index 0705adda54..50e66a4126 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md @@ -28,6 +28,10 @@ A [`DSSInstancesResource`](../../../../resources/astm/f3411/dss.py) from which a [`IDGeneratorResource`](../../../../resources/interuss/id_generator.py) providing the Subscription ID for this scenario. +### uss_identification + +[`USSIdentificationResource`](../../../../resources/interuss/uss_identification.py) describing how to identify participants responsible for observed notifications. + ## Setup test case ### [Clean workspace test step](./dss/test_steps/clean_workspace.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md index 8646eead8d..9721612172 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md @@ -28,6 +28,10 @@ A [`DSSInstancesResource`](../../../../resources/astm/f3411/dss.py) from which a [`IDGeneratorResource`](../../../../resources/interuss/id_generator.py) providing the Subscription ID for this scenario. +### uss_identification + +[`USSIdentificationResource`](../../../../resources/interuss/uss_identification.py) describing how to identify participants responsible for observed notifications. + ## Setup test case ### [Clean workspace test step](./dss/test_steps/clean_workspace.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py index 9ae8f32650..120e58ce5a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from typing import Any from uas_standards.astm.f3548.v21.api import ( ConstraintReference, @@ -343,6 +344,7 @@ def _validate_cr_from_secondary( involved_participants: list[str], from_search: bool = False, ): + check_args: dict[str, Any] = {} with self.check(main_check_name, involved_participants) as main_check: with self.check( "Propagated constraint reference contains the correct manager", diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py index f9f20703ed..7232a12920 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from typing import Any from uas_standards.astm.f3548.v21.api import ( EntityID, @@ -351,7 +352,7 @@ def _validate_oir_from_secondary( involved_participants: list[str], ): # TODO: this main check mechanism may be removed if we are able to specify requirements to be validated in test step fragments - + check_args: dict[str, Any] = {} with self.check(main_check_name, involved_participants) as main_check: with self.check( "Propagated operational intent reference contains the correct manager", diff --git a/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py b/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py index b679fa4e77..39aede2757 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py @@ -9,7 +9,7 @@ Interaction, QueryDirection, ) -from monitoring.monitorlib.fetch import Query, QueryError +from monitoring.monitorlib.fetch import Query, QueryError, QueryType from monitoring.uss_qualifier.resources.interuss.mock_uss.client import MockUSSClient from monitoring.uss_qualifier.scenarios.scenario import TestScenarioType @@ -132,3 +132,16 @@ def is_applicable(interaction: Interaction) -> bool: return interaction.query.status_code == status_code return is_applicable + + +def query_type_filter(*query_type: QueryType | None) -> Callable[[Interaction], bool]: + def is_applicable(interaction: Interaction) -> bool: + if "query_type" in interaction.query and interaction.query.query_type: + if interaction.query.query_type in query_type: + return True + else: + if None in query_type: + return True + return False + + return is_applicable diff --git a/monitoring/uss_qualifier/scenarios/scenario.py b/monitoring/uss_qualifier/scenarios/scenario.py index 2d121282ee..683e0752b8 100644 --- a/monitoring/uss_qualifier/scenarios/scenario.py +++ b/monitoring/uss_qualifier/scenarios/scenario.py @@ -14,7 +14,7 @@ from monitoring import uss_qualifier as uss_qualifier_module from monitoring.monitorlib import fetch, inspection from monitoring.monitorlib.errors import current_stack_string -from monitoring.monitorlib.fetch import QueryType +from monitoring.monitorlib.fetch import Query, QueryType from monitoring.monitorlib.inspection import fullname from monitoring.monitorlib.temporal import TestTimeContext from monitoring.uss_qualifier import scenarios as scenarios_module @@ -127,6 +127,7 @@ def record_failed( details: str = "", query_timestamps: list[datetime] | None = None, additional_data: dict | None = None, + queries: Query | Iterable[Query] | None = None, ) -> None: self._outcome_recorded = True if "severity" in self._documentation and self._documentation.severity: @@ -159,10 +160,24 @@ def record_failed( } if additional_data is not None: kwargs["additional_data"] = additional_data - if query_timestamps is not None: - kwargs["query_report_timestamps"] = [ - StringBasedDateTime(t) for t in query_timestamps - ] + if query_timestamps is not None or queries is not None: + query_report_timestamps = [] + if query_timestamps is not None: + query_report_timestamps.extend( + StringBasedDateTime(t) for t in query_timestamps + ) + if isinstance(queries, Query): + if "initiated_at" in queries.request and queries.request.initiated_at: + query_report_timestamps.append( + StringBasedDateTime(queries.request.initiated_at) + ) + elif queries: + for query in queries: + if "initiated_at" in query.request and query.request.initiated_at: + query_report_timestamps.append( + StringBasedDateTime(query.request.initiated_at) + ) + kwargs["query_report_timestamps"] = query_report_timestamps failed_check = FailedCheck(**kwargs) self._step_report.failed_checks.append(failed_check) if self._on_failed_check is not None: diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml index 2312792d69..94f0f16277 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml @@ -85,6 +85,7 @@ actions: mock_uss: mock_uss_dp id_generator: id_generator dss_pool: dss_instances + uss_identification: uss_identification on_failure: Continue - test_scenario: scenario_type: scenarios.astm.netrid.v19.NominalBehavior diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml index f0032054a8..7aeee5a536 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml @@ -94,6 +94,7 @@ actions: mock_uss: mock_uss_dp id_generator: id_generator dss_pool: dss_instances + uss_identification: uss_identification on_failure: Continue - test_scenario: scenario_type: scenarios.astm.netrid.v22a.NominalBehavior diff --git a/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json b/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json index 2be17a27ec..36c3d4b25f 100644 --- a/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json +++ b/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json @@ -17,8 +17,8 @@ "null" ] }, - "astm_url_regexes": { - "description": "If a URL to an ASTM (F3411, F3548, etc) endpoint matches one of these regular expressions, assume the participant is responsible for that server", + "server_url_regexes": { + "description": "If a URL to an endpoint matches one of these regular expressions, assume the participant is responsible for that server", "items": { "type": "string" },