Skip to content

Commit

Permalink
[uss_qualifier] MockUssClient and test steps with FlightPlannerClient (
Browse files Browse the repository at this point in the history
…interuss#347)

* Added V1FlightPlannerClient to MockUssClient; added test steps to plan flight using FlightPlannerClient

* Removing unwanted port mapping

* Removing unwanted import

* Update monitoring/uss_qualifier/resources/interuss/mock_uss/client.py

Co-authored-by: Benjamin Pelletier <[email protected]>

* Fixing per PR review comments

* Remove an unwanted import

* Adding missing code

* Removing code causing failure

* Fix bug

* Fix format

* Fix bug

* Update monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py

Co-authored-by: Benjamin Pelletier <[email protected]>

* Fixing PR review comments

* Removing if condition not required

* Fix per PR review

* Nit fix per review

---------

Co-authored-by: Benjamin Pelletier <[email protected]>
  • Loading branch information
punamverma and BenjaminPelletier authored Nov 20, 2023
1 parent 190def8 commit 58fdbcb
Show file tree
Hide file tree
Showing 13 changed files with 637 additions and 56 deletions.
2 changes: 1 addition & 1 deletion monitoring/mock_uss/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ services:
- MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem
- MOCK_USS_TOKEN_AUDIENCE=scdsc.log.uss6.localutm,localhost,host.docker.internal
- MOCK_USS_BASE_URL=http://scdsc.log.uss6.localutm
- MOCK_USS_SERVICES=scdsc,interaction_logging
- MOCK_USS_SERVICES=scdsc,interaction_logging,flight_planning
- MOCK_USS_INTERACTIONS_LOG_DIR=output/scdsc_interaction_logs
- MOCK_USS_PORT=80
expose:
Expand Down
1 change: 1 addition & 0 deletions monitoring/mock_uss/start_all_local_mocks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ monitoring/mock_uss/wait_for_mock_uss.sh mock_uss_riddp_v19
monitoring/mock_uss/wait_for_mock_uss.sh mock_uss_ridsp
monitoring/mock_uss/wait_for_mock_uss.sh mock_uss_riddp
monitoring/mock_uss/wait_for_mock_uss.sh mock_uss_tracer
monitoring/mock_uss/wait_for_mock_uss.sh mock_uss_scdsc_interaction_log
10 changes: 9 additions & 1 deletion monitoring/monitorlib/clients/flight_planning/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import List, Optional, Union
from typing import Optional, Set

from monitoring.monitorlib.clients.flight_planning.test_preparation import (
TestPreparationActivityResponse,
Expand All @@ -15,6 +15,7 @@
)
from monitoring.monitorlib.fetch import QueryError
from monitoring.monitorlib.geotemporal import Volume4D
from monitoring.uss_qualifier.configurations.configuration import ParticipantID


class PlanningActivityError(QueryError):
Expand All @@ -24,6 +25,13 @@ class PlanningActivityError(QueryError):
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."""

participant_id: ParticipantID
created_flight_ids: Set[FlightID]

def __init__(self, participant_id: ParticipantID):
self.participant_id = participant_id
self.created_flight_ids: Set[FlightID] = set()

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

@abstractmethod
Expand Down
16 changes: 15 additions & 1 deletion monitoring/monitorlib/clients/flight_planning/client_scd.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
from monitoring.monitorlib.fetch import query_and_describe
from monitoring.monitorlib.geotemporal import Volume4D
from monitoring.monitorlib.infrastructure import UTMClientSession
from monitoring.uss_qualifier.configurations.configuration import ParticipantID


class SCDFlightPlannerClient(FlightPlannerClient):
SCD_SCOPE = scd_api_constants.Scope.Inject
_session: UTMClientSession
_plan_statuses: Dict[FlightID, FlightPlanStatus]

def __init__(self, session: UTMClientSession):
def __init__(self, session: UTMClientSession, participant_id: ParticipantID):
super(SCDFlightPlannerClient, self).__init__(participant_id=participant_id)
self._session = session
self._plan_statuses = {}

Expand Down Expand Up @@ -120,6 +122,16 @@ def _inject(
scd_api.InjectFlightResponseResult.NotSupported: old_state,
}[resp.result],
)

created_status = [
FlightPlanStatus.Planned,
FlightPlanStatus.OkToFly,
FlightPlanStatus.OffNominal,
]
if response.activity_result == PlanningActivityResult.Completed:
if response.flight_plan_status in created_status:
self.created_flight_ids.add(flight_id)

self._plan_statuses[flight_id] = response.flight_plan_status
return response

Expand Down Expand Up @@ -183,6 +195,8 @@ def try_end_flight(
)
if resp.result == scd_api.DeleteFlightResponseResult.Closed:
del self._plan_statuses[flight_id]
self.created_flight_ids.discard(flight_id)

else:
self._plan_statuses[flight_id] = response.flight_plan_status
return response
Expand Down
33 changes: 20 additions & 13 deletions monitoring/monitorlib/clients/flight_planning/client_v1.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import json
import uuid
from typing import Optional

from implicitdict import ImplicitDict
from monitoring.monitorlib.clients.flight_planning.client import (
FlightPlannerClient,
PlanningActivityError,
)
from monitoring.monitorlib.clients.flight_planning.test_preparation import (
TestPreparationActivityResponse,
)

from monitoring.monitorlib.clients.flight_planning.flight_info import (
FlightInfo,
FlightID,
Expand All @@ -23,18 +19,21 @@
from monitoring.monitorlib.geotemporal import Volume4D
from monitoring.monitorlib.infrastructure import UTMClientSession
from monitoring.uss_qualifier.configurations.configuration import ParticipantID

from monitoring.monitorlib.clients.flight_planning.client import PlanningActivityError
from monitoring.monitorlib.clients.flight_planning.planning import (
PlanningActivityResult,
FlightPlanStatus,
)
from uas_standards.interuss.automated_testing.flight_planning.v1 import api
from uas_standards.interuss.automated_testing.flight_planning.v1.constants import Scope


class V1FlightPlannerClient(FlightPlannerClient):
_session: UTMClientSession
_participant_id: ParticipantID

def __init__(self, session: UTMClientSession, participant_id: ParticipantID):
super(V1FlightPlannerClient, self).__init__(participant_id=participant_id)
self._session = session
self._participant_id = participant_id

def _inject(
self,
Expand All @@ -61,7 +60,7 @@ def _inject(
url,
json=req,
scope=Scope.Plan,
participant_id=self._participant_id,
participant_id=self.participant_id,
query_type=QueryType.InterUSSFlightPlanningV1UpsertFlightPlan,
)
if query.status_code != 200 and query.status_code != 201:
Expand All @@ -78,6 +77,15 @@ def _inject(
f"Response to plan flight could not be parsed: {str(e)}", query
)

created_status = [
FlightPlanStatus.Planned,
FlightPlanStatus.OkToFly,
FlightPlanStatus.OffNominal,
]
if resp.planning_result == PlanningActivityResult.Completed:
if resp.flight_plan_status in created_status:
self.created_flight_ids.add(flight_plan_id)

response = PlanningActivityResponse(
flight_id=flight_plan_id,
queries=[query],
Expand Down Expand Up @@ -122,7 +130,7 @@ def try_end_flight(
op.verb,
url,
scope=Scope.Plan,
participant_id=self._participant_id,
participant_id=self.participant_id,
query_type=QueryType.InterUSSFlightPlanningV1DeleteFlightPlan,
)
if query.status_code != 200:
Expand All @@ -138,7 +146,7 @@ def try_end_flight(
raise PlanningActivityError(
f"Response to delete flight plan could not be parsed: {str(e)}", query
)

self.created_flight_ids.discard(flight_id)
response = PlanningActivityResponse(
flight_id=flight_id,
queries=[query],
Expand All @@ -154,7 +162,7 @@ def report_readiness(self) -> TestPreparationActivityResponse:
op.verb,
op.path,
scope=Scope.DirectAutomatedTest,
participant_id=self._participant_id,
participant_id=self.participant_id,
query_type=QueryType.InterUSSFlightPlanningV1GetStatus,
)
if query.status_code != 200:
Expand Down Expand Up @@ -192,7 +200,7 @@ def clear_area(self, area: Volume4D) -> TestPreparationActivityResponse:
op.path,
json=req,
scope=Scope.DirectAutomatedTest,
participant_id=self._participant_id,
participant_id=self.participant_id,
query_type=QueryType.InterUSSFlightPlanningV1ClearArea,
)
if query.status_code != 200:
Expand All @@ -208,7 +216,6 @@ def clear_area(self, area: Volume4D) -> TestPreparationActivityResponse:
raise PlanningActivityError(
f"Response to clear area could not be parsed: {str(e)}", query
)

if resp.outcome.success:
errors = None
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from typing import Dict

from implicitdict import ImplicitDict
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Tuple, Optional, Set
from urllib.parse import urlparse
from implicitdict import ImplicitDict

from monitoring.monitorlib import infrastructure, fetch
from monitoring.monitorlib.clients.flight_planning.client import (
PlanningActivityError,
Expand Down Expand Up @@ -79,7 +78,7 @@ def to_client(
session = infrastructure.UTMClientSession(
self.scd_injection_base_url, auth_adapter, self.timeout_seconds
)
return SCDFlightPlannerClient(session)
return SCDFlightPlannerClient(session, self.participant_id)
elif "v1_base_url" in self and self.v1_base_url:
session = infrastructure.UTMClientSession(
self.v1_base_url, auth_adapter, self.timeout_seconds
Expand Down Expand Up @@ -123,7 +122,6 @@ def request_flight(
self,
request: InjectFlightRequest,
flight_id: Optional[str] = None,
additional_fields: Optional[dict] = None,
) -> Tuple[InjectFlightResponse, fetch.Query, str]:
usage_states = {
OperationalIntentState.Accepted: AirspaceUsageState.Planned,
Expand Down Expand Up @@ -171,7 +169,7 @@ def request_flight(
if not flight_id:
try:
resp = self.client.try_plan_flight(
flight_info, ExecutionStyle.IfAllowed, additional_fields
flight_info, ExecutionStyle.IfAllowed
)
except PlanningActivityError as e:
raise QueryError(str(e), e.queries)
Expand Down
25 changes: 21 additions & 4 deletions monitoring/uss_qualifier/resources/interuss/mock_uss/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
from implicitdict import ImplicitDict

from monitoring.monitorlib import fetch
from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient
from monitoring.monitorlib.clients.flight_planning.client_v1 import (
V1FlightPlannerClient,
)
from monitoring.monitorlib.clients.mock_uss.locality import (
GetLocalityResponse,
PutLocalityRequest,
Expand All @@ -24,22 +28,28 @@
from typing import Tuple, List
from implicitdict import StringBasedDateTime


MOCK_USS_CONFIG_SCOPE = "interuss.mock_uss.configure"


class MockUSSClient(object):
"""Means to communicate with an InterUSS mock_uss instance"""

flight_planner: FlightPlannerClient

def __init__(
self,
participant_id: str,
base_url: str,
auth_adapter: AuthAdapter,
timeout_seconds: Optional[float] = None,
):
self.base_url = base_url
self.session = UTMClientSession(base_url, auth_adapter)
self.session = UTMClientSession(base_url, auth_adapter, timeout_seconds)
self.participant_id = participant_id
v1_base_url = base_url + "/flight_planning/v1"
self.flight_planner = V1FlightPlannerClient(
UTMClientSession(v1_base_url, auth_adapter, timeout_seconds), participant_id
)

def get_status(self) -> fetch.Query:
return fetch.query_and_describe(
Expand Down Expand Up @@ -77,7 +87,9 @@ def set_locality(self, locality_code: LocalityCode) -> fetch.Query:

# TODO: Add other methods to interact with the mock USS in other ways (like starting/stopping message signing data collection)

def get_interactions(self, from_time: StringBasedDateTime) -> List[Interaction]:
def get_interactions(
self, from_time: StringBasedDateTime
) -> Tuple[List[Interaction], fetch.Query]:
"""
Requesting interuss interactions from mock_uss from a given time till now
Args:
Expand Down Expand Up @@ -108,7 +120,8 @@ def get_interactions(self, from_time: StringBasedDateTime) -> List[Interaction]:
msg=f"RecordedInteractionsResponse from mock_uss response contained invalid JSON: {str(e)}",
queries=[query],
)
return response.interactions

return response.interactions, query


class MockUSSSpecification(ImplicitDict):
Expand All @@ -124,6 +137,9 @@ class MockUSSSpecification(ImplicitDict):
participant_id: ParticipantID
"""Test participant responsible for this mock USS."""

timeout_seconds: Optional[float] = None
"""Number of seconds to allow for requests to this mock_uss instance. If None, use default."""


class MockUSSResource(Resource[MockUSSSpecification]):
mock_uss: MockUSSClient
Expand All @@ -137,6 +153,7 @@ def __init__(
specification.participant_id,
specification.mock_uss_base_url,
auth_adapter.adapter,
specification.timeout_seconds,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ No notification pushed by control_uss to tested_uss, will ensure that tested_uss
while planning a nearby flight.
If a notification is sent to tested_uss, the precondition for running this scenario will not be satisfied.

### [Test_uss attempts to plan flight 1, expect failure test step](test_steps/plan_flight_intent_expect_failed.md)
### [Tested_uss attempts to plan flight 1, expect failure test step](test_steps/plan_flight_intent_expect_failed.md)
The test driver attempts to plan the flight 1 via the tested_uss. It checks if any conflicts with flight 2
which is of equal priority and came first.

Expand Down
Loading

0 comments on commit 58fdbcb

Please sign in to comment.