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 f94cd07 commit d89f7f3
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 12 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
all_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_instance = 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,93 @@
import ipaddress
import socket
import time
import uuid
from dataclasses import dataclass
import datetime
from enum import Enum
from typing import List, Dict, Optional
from urllib.parse import urlparse

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

from monitoring.monitorlib.fetch.rid import ISA
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.astm.netrid.dss_wrapper import DSSWrapper
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()

raise ValueError("IT RAN")

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
on_failure: Continue
Loading

0 comments on commit d89f7f3

Please sign in to comment.