Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
848 changes: 116 additions & 732 deletions .basedpyright/baseline.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*'
Original file line number Diff line number Diff line change
Expand Up @@ -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.*'
86 changes: 77 additions & 9 deletions monitoring/uss_qualifier/resources/interuss/uss_identification.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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]]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may deserve a note in the NEXT_RELEASE_NOTES.md

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely; thanks for the catch

"""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]
Expand All @@ -40,18 +48,67 @@ 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

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:
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
Loading
Loading