Skip to content

Commit

Permalink
Merge remote-tracking branch 'interuss/main' into idempotency
Browse files Browse the repository at this point in the history
  • Loading branch information
BenjaminPelletier committed Oct 11, 2023
2 parents ed78dd7 + 5d7821e commit c762dbf
Show file tree
Hide file tree
Showing 65 changed files with 2,511 additions and 399 deletions.
2 changes: 2 additions & 0 deletions monitoring/mock_uss/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
webapp.config.get(config.KEY_TOKEN_PUBLIC_KEY),
webapp.config.get(config.KEY_TOKEN_AUDIENCE),
)

MOCK_USS_CONFIG_SCOPE = "interuss.mock_uss.configure"
5 changes: 1 addition & 4 deletions monitoring/mock_uss/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from monitoring.mock_uss import import_environment_variable
from monitoring.monitorlib import auth_validation
from monitoring.monitorlib.locality import Locality


KEY_TOKEN_PUBLIC_KEY = "MOCK_USS_PUBLIC_KEY"
Expand All @@ -27,7 +26,5 @@
mutator=lambda s: set(svc.strip().lower() for svc in s.split(",")),
)
import_environment_variable(KEY_DSS_URL, required=False)
import_environment_variable(
KEY_BEHAVIOR_LOCALITY, default="CHE", mutator=Locality.from_locale
)
import_environment_variable(KEY_BEHAVIOR_LOCALITY, default="US.IndustryCollaboration")
import_environment_variable(KEY_CODE_VERSION, default="Unknown")
Empty file.
29 changes: 29 additions & 0 deletions monitoring/mock_uss/dynamic_configuration/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json

from implicitdict import ImplicitDict
from monitoring.mock_uss import require_config_value, webapp
from monitoring.mock_uss.config import KEY_BEHAVIOR_LOCALITY
from monitoring.monitorlib.locality import Locality, LocalityCode
from monitoring.monitorlib.multiprocessing import SynchronizedValue


require_config_value(KEY_BEHAVIOR_LOCALITY)


class DynamicConfiguration(ImplicitDict):
locale: LocalityCode


db = SynchronizedValue(
DynamicConfiguration(locale=LocalityCode(webapp.config[KEY_BEHAVIOR_LOCALITY])),
decoder=lambda b: ImplicitDict.parse(
json.loads(b.decode("utf-8")), DynamicConfiguration
),
capacity_bytes=10000,
)


def get_locality() -> Locality:
with db as tx:
code = tx.locale
return Locality.from_locale(code)
48 changes: 48 additions & 0 deletions monitoring/mock_uss/dynamic_configuration/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Tuple

import flask
from implicitdict import ImplicitDict

from monitoring.mock_uss import webapp
from monitoring.mock_uss.auth import requires_scope, MOCK_USS_CONFIG_SCOPE
from monitoring.mock_uss.dynamic_configuration.configuration import db, get_locality
from monitoring.monitorlib.clients.mock_uss.locality import (
PutLocalityRequest,
GetLocalityResponse,
)
from monitoring.monitorlib.locality import Locality


@webapp.route("/configuration/locality", methods=["GET"])
def locality_get() -> Tuple[str, int]:
return flask.jsonify(
GetLocalityResponse(locality_code=get_locality().locality_code())
)


@webapp.route("/configuration/locality", methods=["PUT"])
@requires_scope([MOCK_USS_CONFIG_SCOPE]) # TODO: use separate public key for this
def locality_set() -> Tuple[str, int]:
"""Set the locality of the mock_uss."""
try:
json = flask.request.json
if json is None:
raise ValueError("Request did not contain a JSON payload")
req: PutLocalityRequest = ImplicitDict.parse(json, PutLocalityRequest)
except ValueError as e:
msg = f"Change locality unable to parse JSON: {str(e)}"
return msg, 400

# Make sure this is a valid locality
try:
Locality.from_locale(req.locality_code)
except ValueError as e:
msg = f"Invalid locality_code: {str(e)}"
return msg, 400

with db as tx:
tx.locale = req.locality_code

return flask.jsonify(
GetLocalityResponse(locality_code=get_locality().locality_code())
)
2 changes: 1 addition & 1 deletion monitoring/mock_uss/interaction_logging/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def interaction_log_after_request(response):
datetime.datetime.utcnow() - flask.current_app.custom_profiler["start"]
).total_seconds()
# TODO: Make this configurable instead of hardcoding exactly these query types
if "/uss/v1/" in flask.request.url_rule.rule:
if flask.request.url_rule is not None and "/uss/v1/" in flask.request.url_rule.rule:
query = describe_flask_query(flask.request, response, elapsed_s)
log_interaction(QueryDirection.Incoming, query)
return response
3 changes: 3 additions & 0 deletions monitoring/mock_uss/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ def handle_exception(e):
flask.jsonify({"message": "Unhandled {}: {}".format(type(e).__name__, str(e))}),
500,
)


from .dynamic_configuration import routes
9 changes: 9 additions & 0 deletions monitoring/mock_uss/scdsc/flight_planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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


class PlanningError(Exception):
Expand Down Expand Up @@ -47,6 +48,7 @@ def validate_request(req_body: scd_api.InjectFlightRequest, locality: Locality)
# Validate max planning horizon for creation
start_time = Volume4DCollection.from_interuss_scd_api(
req_body.operational_intent.volumes
+ req_body.operational_intent.off_nominal_volumes
).time_start.datetime
time_delta = start_time - datetime.now(tz=start_time.tzinfo)
if (
Expand Down Expand Up @@ -87,6 +89,13 @@ def check_for_disallowed_conflicts(
if log is None:
log = lambda msg: None

if req_body.operational_intent.state not in (
OperationalIntentState.Accepted,
OperationalIntentState.Activated,
):
# No conflicts are disallowed if the flight is not nominal
return

v1 = Volume4DCollection.from_interuss_scd_api(req_body.operational_intent.volumes)

for op_intent in op_intents:
Expand Down
13 changes: 8 additions & 5 deletions monitoring/mock_uss/scdsc/routes_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import requests.exceptions

from monitoring.monitorlib.idempotency import idempotent_request

from monitoring.mock_uss.dynamic_configuration.configuration import get_locality
from uas_standards.interuss.automated_testing.scd.v1.api import (
InjectFlightRequest,
InjectFlightResponse,
Expand Down Expand Up @@ -41,7 +43,7 @@
from monitoring.mock_uss.scdsc.routes_scdsc import op_intent_from_flightrecord
from monitoring.monitorlib.geo import Polygon
from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection
from monitoring.mock_uss.config import KEY_BASE_URL, KEY_BEHAVIOR_LOCALITY
from monitoring.mock_uss.config import KEY_BASE_URL
from monitoring.monitorlib import versioning
from monitoring.monitorlib.clients import scd as scd_client
from monitoring.monitorlib.fetch import QueryError
Expand All @@ -55,7 +57,6 @@


require_config_value(KEY_BASE_URL)
require_config_value(KEY_BEHAVIOR_LOCALITY)

DEADLOCK_TIMEOUT = timedelta(seconds=5)

Expand Down Expand Up @@ -167,7 +168,7 @@ def scdsc_inject_flight(flight_id: str) -> Tuple[str, int]:

def inject_flight(flight_id: str, req_body: InjectFlightRequest) -> Tuple[dict, int]:
pid = os.getpid()
locality = webapp.config[KEY_BEHAVIOR_LOCALITY]
locality = get_locality()

def log(msg: str):
logger.debug(f"[inject_flight/{pid}:{flight_id}] {msg}")
Expand Down Expand Up @@ -231,9 +232,11 @@ def log(msg: str):
# Check for operational intents in the DSS
step_name = "querying for operational intents"
log("Obtaining latest operational intent information")
vol4 = Volume4DCollection.from_interuss_scd_api(
v1 = Volume4DCollection.from_interuss_scd_api(
req_body.operational_intent.volumes
).bounding_volume.to_f3548v21()
+ req_body.operational_intent.off_nominal_volumes
)
vol4 = v1.bounding_volume.to_f3548v21()
op_intents = query_operational_intents(vol4)

# Check for intersections
Expand Down
4 changes: 2 additions & 2 deletions monitoring/mock_uss/templates/tracer/log.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@

{% block content %}
{{ explorer_header() }}
{{ explorer_content(log) }}
{{ explorer_footer() }}
{{ explorer_content("top_node", log) }}
{{ explorer_footer(["top_node"]) }}
{% endblock %}
Empty file.
94 changes: 94 additions & 0 deletions monitoring/monitorlib/clients/flight_planning/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from abc import ABC, abstractmethod
from typing import List, Optional, Union

from monitoring.monitorlib.clients.flight_planning.test_preparation import (
TestPreparationActivityResponse,
)

from monitoring.monitorlib.clients.flight_planning.flight_info import (
FlightInfo,
FlightID,
ExecutionStyle,
)
from monitoring.monitorlib.clients.flight_planning.planning import (
PlanningActivityResponse,
)
from monitoring.monitorlib.fetch import Query
from monitoring.monitorlib.geotemporal import Volume4D


class PlanningActivityError(Exception):
queries: List[Query]

def __init__(
self, message: str, queries: Optional[Union[Query, List[Query]]] = None
):
super(PlanningActivityError, self).__init__(message)
if queries is None:
self.queries = []
elif isinstance(queries, Query):
self.queries = [queries]
else:
self.queries = queries


class FlightPlannerClient(ABC):
"""Client to interact with a USS as a user performing flight planning activities and as the test director preparing for tests involving flight planning activities."""

# ===== Emulation of user actions =====

@abstractmethod
def try_plan_flight(
self, flight_info: FlightInfo, execution_style: ExecutionStyle
) -> PlanningActivityResponse:
"""Instruct the USS to emulate a normal user trying to plan the described flight.
Raises:
* PlanningActivityError
"""
raise NotImplementedError()

@abstractmethod
def try_update_flight(
self,
flight_id: FlightID,
updated_flight_info: FlightInfo,
execution_style: ExecutionStyle,
) -> PlanningActivityResponse:
"""Instruct the USS to emulate a normal user trying to update the specified flight as described.
Raises:
* PlanningActivityError
"""
raise NotImplementedError()

@abstractmethod
def try_end_flight(
self, flight_id: FlightID, execution_style: ExecutionStyle
) -> PlanningActivityResponse:
"""Instruct the USS to emulate a normal user trying to end the specified flight.
Raises:
* PlanningActivityError
"""
raise NotImplementedError()

# ===== Test preparation activities =====

@abstractmethod
def report_readiness(self) -> TestPreparationActivityResponse:
"""Acting as test director, ask the USS about its readiness to use its flight planning interface for automated testing.
Raises:
* PlanningActivityError
"""
raise NotImplementedError()

@abstractmethod
def clear_area(self, area: Volume4D) -> TestPreparationActivityResponse:
"""Acting as test director, instruct the USS to close/end/remove all flights it manages within the specified area.
Raises:
* PlanningActivityError
"""
raise NotImplementedError()
Loading

0 comments on commit c762dbf

Please sign in to comment.