-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'interuss/main' into idempotency
Showing
86 changed files
with
1,487 additions
and
2,287 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
monitoring/uss_qualifier/action_generators/astm/f3548/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .for_each_dss import ForEachDSS |
97 changes: 97 additions & 0 deletions
97
monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .vertices import VerticesResource |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
from .dss import DSSInstanceResource | ||
from .dss import DSSInstanceResource, DSSInstancesResource |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from typing import List | ||
|
||
from implicitdict import ImplicitDict | ||
|
||
from monitoring.monitorlib.geo import LatLngPoint | ||
from monitoring.uss_qualifier.resources.resource import Resource | ||
|
||
|
||
class VerticesSpecification(ImplicitDict): | ||
"""Specifies a list of vertices representing a 2D area. | ||
Useful for passing arbitrary areas to test scenarios.""" | ||
|
||
vertices: List[LatLngPoint] | ||
"""Represents a 2D area""" | ||
|
||
|
||
class VerticesResource(Resource[VerticesSpecification]): | ||
specification: VerticesSpecification | ||
|
||
def __init__(self, specification: VerticesSpecification): | ||
self.specification = specification |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
329 changes: 329 additions & 0 deletions
329
monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,329 @@ | ||
import copy | ||
import datetime | ||
from typing import Optional, List, Dict, Any | ||
|
||
import arrow | ||
import s2sphere | ||
from uas_standards.astm.f3411 import v19, v22a | ||
|
||
from monitoring.monitorlib.fetch import query_and_describe | ||
from monitoring.monitorlib.mutate.rid import ChangedISA | ||
from monitoring.monitorlib.rid import RIDVersion | ||
from monitoring.prober.infrastructure import register_resource_type | ||
from monitoring.uss_qualifier.common_data_definitions import Severity | ||
from monitoring.uss_qualifier.resources import VerticesResource | ||
from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstanceResource | ||
from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource | ||
from monitoring.uss_qualifier.resources.netrid.service_area import ServiceAreaResource | ||
from monitoring.uss_qualifier.scenarios.astm.netrid.common.dss import utils | ||
from monitoring.uss_qualifier.scenarios.astm.netrid.dss_wrapper import DSSWrapper | ||
from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario | ||
|
||
|
||
class ISAValidation(GenericTestScenario): | ||
"""Based on prober/rid/v2/test_isa_validation.py from the legacy prober tool.""" | ||
|
||
ISA_TYPE = register_resource_type(368, "ISA") | ||
|
||
_huge_are: List[s2sphere.LatLng] | ||
|
||
create_isa_path: str | ||
|
||
write_scope: str | ||
|
||
def __init__( | ||
self, | ||
dss: DSSInstanceResource, | ||
id_generator: IDGeneratorResource, | ||
isa: ServiceAreaResource, | ||
problematically_big_area: VerticesResource, | ||
): | ||
super().__init__() | ||
self._dss = ( | ||
dss.dss_instance | ||
) # TODO: delete once _delete_isa_if_exists updated to use dss_wrapper | ||
self._dss_wrapper = DSSWrapper(self, dss.dss_instance) | ||
self._isa_id = id_generator.id_factory.make_id(ISAValidation.ISA_TYPE) | ||
self._isa_version: Optional[str] = None | ||
self._isa = isa.specification | ||
|
||
now = arrow.utcnow().datetime | ||
self._isa_start_time = self._isa.shifted_time_start(now) | ||
self._isa_end_time = self._isa.shifted_time_end(now) | ||
self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] | ||
|
||
self._huge_area = [ | ||
v.as_s2sphere() for v in problematically_big_area.specification.vertices | ||
] | ||
|
||
if self._dss.rid_version == RIDVersion.f3411_19: | ||
self.create_isa_path = v19.api.OPERATIONS[ | ||
v19.api.OperationID.CreateSubscription | ||
].path | ||
self.write_scope = v19.constants.Scope.Write | ||
elif self._dss.rid_version == RIDVersion.f3411_22a: | ||
self.create_isa_path = v22a.api.OPERATIONS[ | ||
v22a.api.OperationID.CreateSubscription | ||
].path | ||
self.write_scope = v22a.constants.Scope.ServiceProvider | ||
else: | ||
ValueError(f"Unsupported RID version '{self._dss.rid_version}'") | ||
|
||
def run(self): | ||
self.begin_test_scenario() | ||
|
||
self._setup_case() | ||
|
||
self.begin_test_case("ISA Validation") | ||
self.begin_test_step("ISA Validation") | ||
|
||
(create_isa_url, json_body) = self._isa_huge_area_check() | ||
|
||
self._isa_empty_vertices_check() | ||
self._isa_start_time_in_past() | ||
self._isa_start_time_after_time_end() | ||
self._isa_vertices_are_valid() | ||
|
||
self._isa_missing_outline(create_isa_url, json_body) | ||
self._isa_missing_volume(create_isa_url, json_body) | ||
|
||
self.end_test_step() | ||
self.end_test_case() | ||
self.end_test_scenario() | ||
|
||
def _setup_case(self): | ||
self.begin_test_case("Setup") | ||
|
||
def _ensure_clean_workspace_step(): | ||
self.begin_test_step("Ensure clean workspace") | ||
|
||
self._delete_isa_if_exists() | ||
|
||
self.end_test_step() | ||
|
||
_ensure_clean_workspace_step() | ||
|
||
self.end_test_case() | ||
|
||
def _delete_isa_if_exists(self): | ||
utils.delete_isa_if_exists( | ||
self, | ||
isa_id=self._isa_id, | ||
rid_version=self._dss.rid_version, | ||
session=self._dss.client, | ||
server_id=self._dss_wrapper.participant_id, | ||
) | ||
|
||
def _isa_huge_area_check(self) -> (str, Dict[str, Any]): | ||
"""Returns the request's URL and json payload for subsequently re-using it. | ||
It is of the following form (note that v19 and v22a have slight differences): | ||
"extents": { | ||
"volume": { | ||
"outline_polygon": { | ||
"vertices": [], | ||
}, | ||
"altitude_lower": 20.0, | ||
"altitude_upper": 400.0, | ||
}, | ||
"time_start": <timestamp>, | ||
"time_end": <timestamp>, | ||
}, | ||
"uss_base_url": <base_url>, | ||
""" | ||
|
||
with self.check("ISA huge area", [self._dss_wrapper.participant_id]) as check: | ||
q = self._dss_wrapper.put_isa_expect_response_code( | ||
check=check, | ||
expected_error_codes={400}, | ||
area_vertices=self._huge_area, | ||
alt_lo=self._isa.altitude_min, | ||
alt_hi=self._isa.altitude_max, | ||
start_time=self._isa_start_time, | ||
end_time=self._isa_end_time, | ||
uss_base_url=self._isa.base_url, | ||
isa_id=self._isa_id, | ||
isa_version=self._isa_version, | ||
) | ||
|
||
return q.dss_query.query.request.url, q.dss_query.query.request.json | ||
|
||
def _isa_empty_vertices_check(self): | ||
|
||
with self.check( | ||
"ISA empty vertices", [self._dss_wrapper.participant_id] | ||
) as check: | ||
self._dss_wrapper.put_isa_expect_response_code( | ||
check=check, | ||
expected_error_codes={400}, | ||
area_vertices=[], | ||
alt_lo=self._isa.altitude_min, | ||
alt_hi=self._isa.altitude_max, | ||
start_time=self._isa_start_time, | ||
end_time=self._isa_end_time, | ||
uss_base_url=self._isa.base_url, | ||
isa_id=self._isa_id, | ||
isa_version=self._isa_version, | ||
) | ||
|
||
def _isa_start_time_in_past(self): | ||
time_start = datetime.datetime.utcnow() - datetime.timedelta(minutes=10) | ||
time_end = time_start + datetime.timedelta(minutes=60) | ||
|
||
with self.check( | ||
"ISA start time in the past", [self._dss_wrapper.participant_id] | ||
) as check: | ||
self._dss_wrapper.put_isa_expect_response_code( | ||
check=check, | ||
expected_error_codes={400}, | ||
area_vertices=self._isa_area, | ||
alt_lo=self._isa.altitude_min, | ||
alt_hi=self._isa.altitude_max, | ||
start_time=time_start, | ||
end_time=time_end, | ||
uss_base_url=self._isa.base_url, | ||
isa_id=self._isa_id, | ||
isa_version=self._isa_version, | ||
) | ||
|
||
def _isa_start_time_after_time_end(self): | ||
with self.check( | ||
"ISA start time after end time", [self._dss_wrapper.participant_id] | ||
) as check: | ||
self._dss_wrapper.put_isa_expect_response_code( | ||
check=check, | ||
expected_error_codes={400}, | ||
area_vertices=self._isa_area, | ||
alt_lo=self._isa.altitude_min, | ||
alt_hi=self._isa.altitude_max, | ||
start_time=self._isa.time_end.datetime, | ||
end_time=self._isa.time_start.datetime, | ||
uss_base_url=self._isa.base_url, | ||
isa_id=self._isa_id, | ||
isa_version=self._isa_version, | ||
) | ||
|
||
def _isa_vertices_are_valid(self): | ||
INVALID_VERTICES: List[s2sphere.LatLng] = [ | ||
s2sphere.LatLng.from_degrees(lat=130, lng=-23), | ||
s2sphere.LatLng.from_degrees(lat=130, lng=-24), | ||
s2sphere.LatLng.from_degrees(lat=132, lng=-24), | ||
s2sphere.LatLng.from_degrees(lat=132, lng=-23), | ||
] | ||
|
||
with self.check( | ||
"ISA vertices are valid", [self._dss_wrapper.participant_id] | ||
) as check: | ||
self._dss_wrapper.put_isa_expect_response_code( | ||
check=check, | ||
expected_error_codes={400}, | ||
area_vertices=INVALID_VERTICES, | ||
alt_lo=self._isa.altitude_min, | ||
alt_hi=self._isa.altitude_max, | ||
start_time=self._isa.time_start.datetime, | ||
end_time=self._isa.time_end.datetime, | ||
uss_base_url=self._isa.base_url, | ||
isa_id=self._isa_id, | ||
isa_version=self._isa_version, | ||
) | ||
|
||
def _isa_missing_outline(self, create_isa_url: str, json_body: Dict[str, Any]): | ||
payload = copy.deepcopy(json_body) | ||
if self._dss.rid_version == RIDVersion.f3411_19: | ||
del payload["extents"]["spatial_volume"]["footprint"] | ||
elif self._dss.rid_version == RIDVersion.f3411_22a: | ||
del payload["extents"]["volume"]["outline_polygon"] | ||
|
||
with self.check( | ||
"ISA missing outline", [self._dss_wrapper.participant_id] | ||
) as check: | ||
q = query_and_describe( | ||
client=self._dss.client, | ||
verb="PUT", | ||
url=create_isa_url, | ||
scope=self.write_scope, | ||
json=payload, | ||
) | ||
if self._dss.rid_version == RIDVersion.f3411_19: | ||
rid_query = ChangedISA(v19_query=q) | ||
elif self._dss.rid_version == RIDVersion.f3411_22a: | ||
rid_query = ChangedISA(v22a_query=q) | ||
else: | ||
raise ValueError(f"Unknown RID version: {self._dss.rid_version}") | ||
|
||
self._dss_wrapper._handle_query_result( | ||
check=check, | ||
q=rid_query, | ||
fail_msg="ISA Creation with missing outline has unexpected result code", | ||
required_status_code={400}, | ||
severity=Severity.High, | ||
) | ||
|
||
def _isa_missing_volume(self, create_isa_url: str, json_body: Dict[str, Any]): | ||
payload = copy.deepcopy(json_body) | ||
if self._dss.rid_version == RIDVersion.f3411_19: | ||
del payload["extents"]["spatial_volume"] | ||
elif self._dss.rid_version == RIDVersion.f3411_22a: | ||
del payload["extents"]["volume"] | ||
|
||
with self.check( | ||
"ISA missing volume", [self._dss_wrapper.participant_id] | ||
) as check: | ||
q = query_and_describe( | ||
client=self._dss.client, | ||
verb="PUT", | ||
url=create_isa_url, | ||
scope=self.write_scope, | ||
json=payload, | ||
) | ||
if self._dss.rid_version == RIDVersion.f3411_19: | ||
rid_query = ChangedISA(v19_query=q) | ||
elif self._dss.rid_version == RIDVersion.f3411_22a: | ||
rid_query = ChangedISA(v22a_query=q) | ||
else: | ||
raise ValueError(f"Unknown RID version: {self._dss.rid_version}") | ||
|
||
self._dss_wrapper._handle_query_result( | ||
check=check, | ||
q=rid_query, | ||
fail_msg="ISA Creation with missing outline has unexpected result code", | ||
required_status_code={400}, | ||
severity=Severity.High, | ||
) | ||
|
||
def _isa_missing_extents(self, create_isa_url: str, json_body: Dict[str, Any]): | ||
payload = copy.deepcopy(json_body) | ||
del payload["extents"] | ||
|
||
with self.check( | ||
"ISA missing extents", [self._dss_wrapper.participant_id] | ||
) as check: | ||
q = query_and_describe( | ||
client=self._dss.client, | ||
verb="PUT", | ||
url=create_isa_url, | ||
scope=self.write_scope, | ||
json=payload, | ||
) | ||
if self._dss.rid_version == RIDVersion.f3411_19: | ||
rid_query = ChangedISA(v19_query=q) | ||
elif self._dss.rid_version == RIDVersion.f3411_22a: | ||
rid_query = ChangedISA(v22a_query=q) | ||
else: | ||
raise ValueError(f"Unknown RID version: {self._dss.rid_version}") | ||
|
||
self._dss_wrapper._handle_query_result( | ||
check=check, | ||
q=rid_query, | ||
fail_msg="ISA Creation with missing outline has unexpected result code", | ||
required_status_code={400}, | ||
severity=Severity.High, | ||
) | ||
|
||
def cleanup(self): | ||
self.begin_cleanup() | ||
|
||
self._delete_isa_if_exists() | ||
|
||
self.end_cleanup() |
62 changes: 62 additions & 0 deletions
62
monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from typing import Optional | ||
|
||
from monitoring.monitorlib.fetch import rid as fetch | ||
from monitoring.monitorlib.mutate import rid as mutate | ||
from monitoring.monitorlib.infrastructure import UTMClientSession | ||
from monitoring.monitorlib.rid import RIDVersion | ||
from monitoring.uss_qualifier.common_data_definitions import Severity | ||
from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario | ||
|
||
|
||
def delete_isa_if_exists( | ||
scenario: GenericTestScenario, | ||
isa_id: str, | ||
rid_version: RIDVersion, | ||
session: UTMClientSession, | ||
server_id: Optional[str] = None, | ||
): | ||
fetched = fetch.isa( | ||
isa_id, | ||
rid_version=rid_version, | ||
session=session, | ||
server_id=server_id, | ||
) | ||
scenario.record_query(fetched.query) | ||
with scenario.check("Successful ISA query", [server_id]) as check: | ||
if not fetched.success and fetched.status_code != 404: | ||
check.record_failed( | ||
"ISA information could not be retrieved", | ||
Severity.High, | ||
f"{server_id} DSS instance returned {fetched.status_code} when queried for ISA {isa_id}", | ||
query_timestamps=[fetched.query.request.timestamp], | ||
) | ||
|
||
if fetched.success: | ||
deleted = mutate.delete_isa( | ||
isa_id, | ||
fetched.isa.version, | ||
rid_version, | ||
session, | ||
server_id=server_id, | ||
) | ||
scenario.record_query(deleted.dss_query.query) | ||
for subscriber_id, notification in deleted.notifications.items(): | ||
scenario.record_query(notification.query) | ||
with scenario.check("Removed pre-existing ISA", [server_id]) as check: | ||
if not deleted.dss_query.success: | ||
check.record_failed( | ||
"Could not delete pre-existing ISA", | ||
Severity.High, | ||
f"Attempting to delete ISA {isa_id} from the {server_id} DSS returned error {deleted.dss_query.status_code}", | ||
query_timestamps=[deleted.dss_query.query.request.timestamp], | ||
) | ||
for subscriber_url, notification in deleted.notifications.items(): | ||
with scenario.check("Notified subscriber", [subscriber_url]) as check: | ||
# TODO: Find a better way to identify a subscriber who couldn't be notified | ||
if not notification.success: | ||
check.record_failed( | ||
"Could not notify ISA subscriber", | ||
Severity.Medium, | ||
f"Attempting to notify subscriber for ISA {isa_id} at {subscriber_url} resulted in {notification.status_code}", | ||
query_timestamps=[notification.query.request.timestamp], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
from .isa_simple import ISASimple | ||
from .isa_validation import ISAValidation | ||
from .subscription_validation import SubscriptionValidation | ||
from .crdb_access import CRDBAccess |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_validation.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# ASTM NetRID DSS: Submitted ISA Validations test scenario | ||
|
||
## Overview | ||
|
||
Perform basic operations on a single DSS instance to create an ISA and query it during its time of applicability and | ||
after its time of applicability. | ||
|
||
## Resources | ||
|
||
### dss | ||
|
||
[`DSSInstanceResource`](../../../../../resources/astm/f3411/dss.py) to be tested in this scenario. | ||
|
||
### id_generator | ||
|
||
[`IDGeneratorResource`](../../../../../resources/interuss/id_generator.py) providing the ISA ID for this scenario. | ||
|
||
### isa | ||
|
||
[`ServiceAreaResource`](../../../../../resources/netrid/service_area.py) describing an ISA to be created. | ||
|
||
### problematically_big_area | ||
|
||
[`VerticesResource`](../../../../../resources/vertices.py) describing an area designed to be too big to be accepted by the DSS. | ||
|
||
## Setup test case | ||
|
||
### Ensure clean workspace test step | ||
|
||
This scenario creates an ISA with a known ID. This step ensures that ISA does not exist before the start of the main | ||
part of the test. | ||
|
||
#### Successful ISA query check | ||
|
||
**[astm.f3411.v19.DSS0030](../../../../../requirements/astm/f3411/v19.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. | ||
|
||
#### Removed pre-existing ISA check | ||
|
||
If an ISA with the intended ID is already present in the DSS, it needs to be removed before proceeding with the test. If that ISA cannot be deleted, then the **[astm.f3411.v19.DSS0030](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the ISA deletion endpoint might not be met. | ||
|
||
#### Notified subscriber check | ||
|
||
When a pre-existing ISA needs to be deleted to ensure a clean workspace, any subscribers to ISAs in that area must be notified (as specified by the DSS). If a notification cannot be delivered, then the **[astm.f3411.v19.NET0710](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the POST ISAs endpoint isn't met. | ||
|
||
|
||
## ISA Validation test case | ||
|
||
### ISA Validation test step | ||
|
||
#### ISA huge area check | ||
|
||
Attempting to put a too large ISA should result in a 400. | ||
|
||
#### ISA empty vertices check | ||
|
||
An ISA with a empty `vertices` array in the `extents.spatial_volume.footprint` field of the ISA creation payload should not result in a successful submission. | ||
|
||
#### ISA start time in the past check | ||
|
||
The DSS must reject ISAs with start times in the past. | ||
|
||
#### ISA start time after end time check | ||
|
||
The DSS must reject ISAs for which the start time is after the end time. | ||
|
||
#### ISA vertices are valid check | ||
|
||
The DSS must reject ISAs with invalid vertices, such as vertices that have latitude or longitude outside meaningful ranges. | ||
|
||
#### ISA missing outline check | ||
|
||
If the outline polygon is missing from the `extents.spatial_volume.footprint` field in the payload of the ISA creation request, | ||
the DSS is expected to reject the request. | ||
|
||
#### ISA missing volume check | ||
|
||
If the outline polygon is missing from the `extents.spatial_volume` field in the payload of the ISA creation request, | ||
the DSS is expected to reject the request. | ||
|
||
#### ISA missing extents check | ||
|
||
If the `extents` field is missing from the payload of the ISA creation request, | ||
the DSS is expected to reject the request. | ||
|
||
## Cleanup | ||
|
||
The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely. | ||
|
||
### Successful ISA query check | ||
|
||
**[astm.f3411.v19.DSS0030](../../../../../requirements/astm/f3411/v19.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. | ||
|
||
### Removed pre-existing ISA check | ||
|
||
If an ISA with the intended ID is still present in the DSS, it needs to be removed before exiting the test. If that ISA cannot be deleted, then the **[astm.f3411.v19.DSS0030](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the ISA deletion endpoint might not be met. | ||
|
||
### Notified subscriber check | ||
|
||
When an ISA is deleted, subscribers must be notified. If a subscriber cannot be notified, that subscriber USS did not correctly implement "POST Identification Service Area" in **[astm.f3411.v19.NET0730](../../../../../requirements/astm/f3411/v19.md)**. |
8 changes: 8 additions & 0 deletions
8
monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_validation.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from monitoring.uss_qualifier.scenarios.astm.netrid.common.dss.isa_validation import ( | ||
ISAValidation as CommonISAValidation, | ||
) | ||
from monitoring.uss_qualifier.scenarios.scenario import TestScenario | ||
|
||
|
||
class ISAValidation(TestScenario, CommonISAValidation): | ||
pass |
1 change: 1 addition & 0 deletions
1
monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
from .isa_simple import ISASimple | ||
from .isa_validation import ISAValidation | ||
from .subscription_validation import SubscriptionValidation | ||
from .crdb_access import CRDBAccess |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_validation.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# ASTM NetRID DSS: Submitted ISA Validations test scenario | ||
|
||
## Overview | ||
|
||
Perform basic operations on a single DSS instance to create an ISA and query it during its time of applicability and | ||
after its time of applicability. | ||
|
||
## Resources | ||
|
||
### dss | ||
|
||
[`DSSInstanceResource`](../../../../../resources/astm/f3411/dss.py) to be tested in this scenario. | ||
|
||
### id_generator | ||
|
||
[`IDGeneratorResource`](../../../../../resources/interuss/id_generator.py) providing the ISA ID for this scenario. | ||
|
||
### isa | ||
|
||
[`ServiceAreaResource`](../../../../../resources/netrid/service_area.py) describing an ISA to be created. | ||
|
||
### problematically_big_area | ||
|
||
[`VerticesResource`](../../../../../resources/vertices.py) describing an area designed to be too big to be accepted by the DSS. | ||
|
||
## Setup test case | ||
|
||
### Ensure clean workspace test step | ||
|
||
This scenario creates an ISA with a known ID. This step ensures that ISA does not exist before the start of the main | ||
part of the test. | ||
|
||
#### Successful ISA query check | ||
|
||
**[astm.f3411.v22a.DSS0030](../../../../../requirements/astm/f3411/v22a.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. | ||
|
||
#### Removed pre-existing ISA check | ||
|
||
If an ISA with the intended ID is already present in the DSS, it needs to be removed before proceeding with the test. If that ISA cannot be deleted, then the **[astm.f3411.v22a.DSS0030](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the ISA deletion endpoint might not be met. | ||
|
||
#### Notified subscriber check | ||
|
||
When a pre-existing ISA needs to be deleted to ensure a clean workspace, any subscribers to ISAs in that area must be notified (as specified by the DSS). If a notification cannot be delivered, then the **[astm.f3411.v22a.NET0710](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the POST ISAs endpoint isn't met. | ||
|
||
|
||
## ISA Validation test case | ||
|
||
### ISA Validation test step | ||
|
||
#### ISA huge area check | ||
|
||
Attempting to put a too large ISA should result in a 400. | ||
|
||
#### ISA empty vertices check | ||
|
||
An ISA with a empty `vertices` array in the `extents.volume.outline_polygon` field of the ISA creation payload should not result in a successful submission. | ||
|
||
#### ISA start time in the past check | ||
|
||
The DSS must reject ISAs with start times in the past. | ||
|
||
#### ISA start time after end time check | ||
|
||
The DSS must reject ISAs for which the start time is after the end time. | ||
|
||
#### ISA vertices are valid check | ||
|
||
The DSS must reject ISAs with invalid vertices, such as vertices that have latitude or longitude outside meaningful ranges. | ||
|
||
#### ISA missing outline check | ||
|
||
If the outline polygon is missing from the `extents.volume.outline_polygon` field in the payload of the ISA creation request, | ||
the DSS is expected to reject the request. | ||
|
||
#### ISA missing volume check | ||
|
||
If the outline polygon is missing from the `extents.volume` field in the payload of the ISA creation request, | ||
the DSS is expected to reject the request. | ||
|
||
#### ISA missing extents check | ||
|
||
If the `extents` field is missing from the payload of the ISA creation request, | ||
the DSS is expected to reject the request. | ||
|
||
## Cleanup | ||
|
||
The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely. | ||
|
||
### Successful ISA query check | ||
|
||
**[astm.f3411.v22a.DSS0030](../../../../../requirements/astm/f3411/v22a.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. | ||
|
||
### Removed pre-existing ISA check | ||
|
||
If an ISA with the intended ID is still present in the DSS, it needs to be removed before exiting the test. If that ISA cannot be deleted, then the **[astm.f3411.v22a.DSS0030](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the ISA deletion endpoint might not be met. | ||
|
||
### Notified subscriber check | ||
|
||
When an ISA is deleted, subscribers must be notified. If a subscriber cannot be notified, that subscriber USS did not correctly implement "POST Identification Service Area" in **[astm.f3411.v22a.NET0730](../../../../../requirements/astm/f3411/v22a.md)**. |
8 changes: 8 additions & 0 deletions
8
monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_validation.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from monitoring.uss_qualifier.scenarios.astm.netrid.common.dss.isa_validation import ( | ||
ISAValidation as CommonISAValidation, | ||
) | ||
from monitoring.uss_qualifier.scenarios.scenario import TestScenario | ||
|
||
|
||
class ISAValidation(TestScenario, CommonISAValidation): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
83 changes: 83 additions & 0 deletions
83
monitoring/uss_qualifier/scenarios/astm/utm/dss_interoperability.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.