Skip to content

Commit

Permalink
UTM DSS0300
Browse files Browse the repository at this point in the history
  • Loading branch information
Shastick committed Oct 12, 2023
1 parent 5d7821e commit 9f2d34c
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .for_each_dss import ForEachDSS
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from typing import Dict, List, Optional

from implicitdict import ImplicitDict

from monitoring.monitorlib.inspection import fullname
from monitoring.uss_qualifier.action_generators.documentation.definitions import (
PotentialGeneratedAction,
)
from monitoring.uss_qualifier.action_generators.documentation.documentation import (
list_potential_actions_for_action_declaration,
)
from monitoring.uss_qualifier.reports.report import TestSuiteActionReport
from monitoring.uss_qualifier.resources.astm.f3548.v21 import (
DSSInstancesResource,
DSSInstanceResource,
)

from monitoring.uss_qualifier.resources.definitions import ResourceID
from monitoring.uss_qualifier.resources.resource import (
ResourceType,
MissingResourceError,
)
from monitoring.uss_qualifier.suites.definitions import TestSuiteActionDeclaration
from monitoring.uss_qualifier.suites.suite import (
ActionGenerator,
TestSuiteAction,
ReactionToFailure,
)


class ForEachDSSSpecification(ImplicitDict):
action_to_repeat: TestSuiteActionDeclaration
"""Test suite action to run for each DSS instance"""

dss_instances_source: ResourceID
"""ID of the resource providing the single DSS instance"""

dss_instance_id: ResourceID
"""Resource IDs of DSS input to the action_to_repeat"""


class ForEachDSS(ActionGenerator[ForEachDSSSpecification]):
_actions: List[TestSuiteAction]
_current_action: int
_failure_reaction: ReactionToFailure

@classmethod
def list_potential_actions(
cls, specification: ForEachDSSSpecification
) -> List[PotentialGeneratedAction]:
return list_potential_actions_for_action_declaration(
specification.action_to_repeat
)

def __init__(
self,
specification: ForEachDSSSpecification,
resources: Dict[ResourceID, ResourceType],
):
if specification.dss_instances_source not in resources:
raise MissingResourceError(
f"Resource ID {specification.dss_instances_source} specified as `dss_instances_source` was not present in the available resource pool",
specification.dss_instances_source,
)
dss_instances_resource: DSSInstancesResource = resources[
specification.dss_instances_source
]
if not isinstance(dss_instances_resource, DSSInstancesResource):
raise ValueError(
f"Expected resource ID {specification.dss_instances_source} to be a {fullname(DSSInstancesResource)} but it was a {fullname(dss_instances_resource.__class__)} instead"
)
dss_instances = dss_instances_resource.dss_instances

self._actions = []
for dss_instance in dss_instances:
modified_resources = {k: v for k, v in resources.items()}
modified_resources[
specification.dss_instance_id
] = DSSInstanceResource.from_dss_instance(dss_instance)

self._actions.append(
TestSuiteAction(specification.action_to_repeat, modified_resources)
)

self._current_action = 0
self._failure_reaction = specification.action_to_repeat.on_failure

def run_next_action(self) -> Optional[TestSuiteActionReport]:
if self._current_action < len(self._actions):
report = self._actions[self._current_action].run()
self._current_action += 1
if not report.successful():
if self._failure_reaction == ReactionToFailure.Abort:
self._current_action = len(self._actions)
return report
else:
return None
1 change: 1 addition & 0 deletions monitoring/uss_qualifier/configurations/dev/f3548.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ v1:
priority_preemption_flights: priority_preemption_flights
invalid_flight_intents: invalid_flight_intents
dss: dss
dss_instances: dss_instances
artifacts:
tested_roles:
report_path: output/tested_roles_f3548
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ f3548:
participant_id: uss1
base_url: http://dss.uss1.localutm
has_private_address: true
dss_instances:
$content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json
resource_type: resources.astm.f3548.v21.DSSInstancesResource
dependencies:
auth_adapter: utm_auth
specification:
dss_instances:
- participant_id: uss1
base_url: http://dss.uss1.localutm
has_private_address: true
- participant_id: uss2
base_url: http://dss.uss2.localutm
has_private_address: true

f3548_single_scenario:
uss1:
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .dss import DSSInstanceResource
from .dss import DSSInstanceResource, DSSInstancesResource
54 changes: 51 additions & 3 deletions monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Tuple, List
from __future__ import annotations
from typing import Tuple, List, Optional
from urllib.parse import urlparse

from implicitdict import ImplicitDict
Expand All @@ -24,6 +25,9 @@ class DSSInstanceSpecification(ImplicitDict):
base_url: str
"""Base URL for the DSS instance according to the ASTM F3548-21 API"""

has_private_address: Optional[bool]
"""Whether this DSS instance is expected to have a private address that is not publicly addressable."""

def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
try:
Expand All @@ -34,16 +38,21 @@ def __init__(self, *args, **kwargs):

class DSSInstance(object):
participant_id: str
base_url: str
has_private_address: bool = False
client: infrastructure.UTMClientSession

def __init__(
self,
participant_id: str,
base_url: str,
has_private_address: Optional[bool],
auth_adapter: infrastructure.AuthAdapter,
):
self.participant_id = participant_id
self._base_url = base_url
self.base_url = base_url
if has_private_address is not None:
self.has_private_address = has_private_address
self.client = infrastructure.UTMClientSession(base_url, auth_adapter)

def find_op_intent(
Expand Down Expand Up @@ -82,6 +91,13 @@ def get_full_op_intent(
).operational_intent
return result, query

def is_same_as(self, other: DSSInstance) -> bool:
return (
self.participant_id == other.participant_id
and self.base_url == other.base_url
and self.has_private_address == other.has_private_address
)


class DSSInstanceResource(Resource[DSSInstanceSpecification]):
dss: DSSInstance
Expand All @@ -92,5 +108,37 @@ def __init__(
auth_adapter: AuthAdapterResource,
):
self.dss = DSSInstance(
specification.participant_id, specification.base_url, auth_adapter.adapter
specification.participant_id,
specification.base_url,
specification.get("has_private_address"),
auth_adapter.adapter,
)

@classmethod
def from_dss_instance(cls, dss_instance: DSSInstance) -> DSSInstanceResource:
self = cls.__new__(cls)
self.dss = dss_instance
return self


class DSSInstancesSpecification(ImplicitDict):
dss_instances: List[DSSInstanceSpecification]


class DSSInstancesResource(Resource[DSSInstancesSpecification]):
dss_instances: List[DSSInstance]

def __init__(
self,
specification: DSSInstancesSpecification,
auth_adapter: AuthAdapterResource,
):
self.dss_instances = [
DSSInstance(
s.participant_id,
s.base_url,
s.has_private_address,
auth_adapter.adapter,
)
for s in specification.dss_instances
]
1 change: 1 addition & 0 deletions monitoring/uss_qualifier/scenarios/astm/utm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from .nominal_planning.conflict_equal_priority_not_permitted.conflict_equal_priority_not_permitted import (
ConflictEqualPriorityNotPermitted,
)
from .dss_interoperability import DSSInteroperability
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ASTM F3548-21 UTM DSS interoperability test scenario

## Overview

TODO: Complete with details once we check more than the prerequisites.

This scenario currently only checks that all specified DSS instances are publicly addressable and reachable.

## Resources

### primary_dss_instance

A resources.astm.f3548.v21.DSSInstanceResource containing the "primary" DSS instance for this scenario.

### all_dss_instances

A resources.astm.f3548.v21.DSSInstancesResource containing at least two DSS instances complying with ASTM F3548-21.

## Prerequisites test case

### Test environment requirements test step

#### DSS instance is publicly addressable check

As per **[astm.f3548.v21.DSS0300](../../../requirements/astm/f3548/v21.md)** the DSS instance should be publicly addressable.
As such, this check will fail if the resolved IP of the DSS host is a private IP address, unless that is explicitly
expected.

#### DSS instance is reachable check
As per **[astm.f3548.v21.DSS0300](../../../requirements/astm/f3548/v21.md)** the DSS instance should be publicly addressable.
As such, this check will fail if the DSS is not reachable with a dummy query.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import ipaddress
import socket
from typing import List
from urllib.parse import urlparse

from uas_standards.astm.f3548.v21.api import Volume4D, Volume3D, Polygon, LatLngPoint

from monitoring.uss_qualifier.common_data_definitions import Severity
from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import (
DSSInstancesResource,
DSSInstanceResource,
DSSInstance,
)
from monitoring.uss_qualifier.scenarios.scenario import TestScenario

VERTICES: List[LatLngPoint] = [
LatLngPoint(lng=130.6205, lat=-23.6558),
LatLngPoint(lng=130.6301, lat=-23.6898),
LatLngPoint(lng=130.6700, lat=-23.6709),
LatLngPoint(lng=130.6466, lat=-23.6407),
]
SHORT_WAIT_SEC = 5


class DSSInteroperability(TestScenario):
_dss_primary: DSSInstance
_dss_others: List[DSSInstance]

def __init__(
self,
primary_dss_instance: DSSInstanceResource,
all_dss_instances: DSSInstancesResource,
):
super().__init__()
self._dss_primary = primary_dss_instance.dss
self._dss_others = [
dss
for dss in all_dss_instances.dss_instances
if not dss.is_same_as(primary_dss_instance.dss)
]

def run(self):

self.begin_test_scenario()

self.begin_test_case("Prerequisites")

self.begin_test_step("Test environment requirements")
self._test_env_reqs()
self.end_test_step()

self.end_test_case()

self.end_test_scenario()

def _test_env_reqs(self):
for dss in [self._dss_primary] + self._dss_others:
with self.check(
"DSS instance is publicly addressable", [dss.participant_id]
) as check:
parsed_url = urlparse(dss.base_url)
ip_addr = socket.gethostbyname(parsed_url.hostname)

if dss.has_private_address:
self.record_note(
f"{dss.participant_id}_private_address",
f"DSS instance (URL: {dss.base_url}, netloc: {parsed_url.netloc}, resolved IP: {ip_addr}) is declared as explicitly having a private address, skipping check",
)
elif ipaddress.ip_address(ip_addr).is_private:
check.record_failed(
summary=f"DSS host {parsed_url.netloc} is not publicly addressable",
severity=Severity.Medium,
participants=[dss.participant_id],
details=f"DSS (URL: {dss.base_url}, netloc: {parsed_url.netloc}, resolved IP: {ip_addr}) is not publicly addressable",
)

with self.check("DSS instance is reachable", [dss.participant_id]) as check:
# dummy search query
dss.find_op_intent(
extent=Volume4D(
volume=Volume3D(outline_polygon=Polygon(vertices=VERTICES))
)
)
24 changes: 24 additions & 0 deletions monitoring/uss_qualifier/suites/astm/utm/dss_probing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--This file is autogenerated via `make format`; do not change manually-->
# DSS testing for ASTM NetRID F3558-21 test suite
[`suites.astm.utm.dss_probing`](./dss_probing.yaml)

## [Actions](../../README.md#actions)

1. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py))

## [Checked requirements](../../README.md#checked-requirements)

<table>
<tr>
<th><a href="../../README.md#package">Package</a></th>
<th><a href="../../README.md#requirement">Requirement</a></th>
<th><a href="../../README.md#status">Status</a></th>
<th><a href="../../README.md#checked-in">Checked in</a></th>
</tr>
<tr>
<td rowspan="1" style="vertical-align:top;"><a href="../../../requirements/astm/f3548/v21.md">astm<br>.f3548<br>.v21</a></td>
<td><a href="../../../requirements/astm/f3548/v21.md">DSS0300</a></td>
<td>Implemented</td>
<td><a href="../../../scenarios/astm/utm/dss_interoperability.md">ASTM F3548-21 UTM DSS interoperability</a></td>
</tr>
</table>
11 changes: 11 additions & 0 deletions monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: DSS testing for ASTM NetRID F3558-21
resources:
dss: resources.astm.f3548.v21.DSSInstanceResource
all_dss_instances: resources.astm.f3548.v21.DSSInstancesResource?
actions:
- test_scenario:
scenario_type: scenarios.astm.utm.DSSInteroperability
resources:
primary_dss_instance: dss
all_dss_instances: all_dss_instances

Loading

0 comments on commit 9f2d34c

Please sign in to comment.