Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ The release notes should contain at least the following sections:
## Mandatory migration tasks

* For any uss_qualifier test configurations using any of the test suites `suites.astm.utm.f3548_21`, `suites.faa.uft.message_signing`, `suites.uspace.flight_auth`, or `suites.uspace.required_services`, the following resources must be changed:
* A new required resource named `conflicting_flights_provider` must be provided. It must be a `resources.resource.ResourceProvidingResource` providing `resources.flight_planning.FlightIntentsResource` instances with spatially separated flight intents for each planner combination used by `ConflictEqualPriorityNotPermitted`. One suitable implementation is `resources.geospatial.TriangularCascadeSoutheastResource` with `conflicting_flights` as its `base_resource`; see [f3548_self_contained](./monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml) for an example. The existing `conflicting_flights` resource is still required for other scenarios.
* A new required resource named `conflicting_flights_provider` must be provided. It must be a `resources.resource.ResourceProvidingResource` providing `resources.flight_planning.FlightIntentsResource` instances with spatially separated flight intents for each planner combination used by `ConflictEqualPriorityNotPermitted`. One suitable implementation is `resources.geospatial.TriangularCascadeSoutheastResource` with `conflicting_flights` as its `base_resource`; see [f3548_self_contained](./monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml) for an example.

## Optional migration tasks

* For uss_qualifier test configurations using `suites.astm.utm.f3548_21`, `suites.faa.uft.message_signing`, `suites.uspace.flight_auth`, or `suites.uspace.required_services`, the `conflicting_flights` input is now optional and its suite resource mapping can be removed. By default, `f3548_21` selects index 0 from `conflicting_flights_provider`. An explicitly supplied `conflicting_flights` still overrides this default for now. Keep any base flight resource declaration needed by the provider.

## Important information
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ v1:
prod_env_version_providers: prod_env_version_providers
flight_planners: flight_planners
flight_planners_to_clear: flight_planners
conflicting_flights: conflicting_flights
conflicting_flights_provider: conflicting_flights_provider
priority_preemption_flights_provider: conflicting_flights_provider
invalid_flight_intents: invalid_flight_intents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ v1:
mock_uss: mock_uss
flight_planners: flight_planners
combination_selector: combination_selector
conflicting_flights: che_conflicting_flights
conflicting_flights_provider: che_conflicting_flights_provider
invalid_flight_intents: che_invalid_flight_intents
non_conflicting_flights: che_non_conflicting_flights
Expand Down
2 changes: 0 additions & 2 deletions monitoring/uss_qualifier/configurations/dev/uspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ v1:
test_env_version_providers: test_env_version_providers?
prod_env_version_providers: prod_env_version_providers?

conflicting_flights: che_conflicting_flights
conflicting_flights_provider: che_conflicting_flights_provider
priority_preemption_flights_provider: che_conflicting_flights_provider
invalid_flight_intents: che_invalid_flight_intents
Expand Down Expand Up @@ -97,7 +96,6 @@ v1:
test_env_version_providers: test_env_version_providers?
prod_env_version_providers: prod_env_version_providers?

conflicting_flights: conflicting_flights
conflicting_flights_provider: conflicting_flights_provider
priority_preemption_flights_provider: priority_preemption_flights_provider
invalid_flight_intents: invalid_flight_intents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ function(env) {
prod_env_version_providers: 'prod_env_version_providers',
flight_planners: 'flight_planners',
flight_planners_to_clear: 'flight_planners_to_clear',
conflicting_flights: 'conflicting_flights',
conflicting_flights_provider: 'conflicting_flights_provider',
priority_preemption_flights_provider: 'conflicting_flights_provider',
invalid_flight_intents: 'invalid_flight_intents',
Expand Down
29 changes: 23 additions & 6 deletions monitoring/uss_qualifier/resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,30 @@ Resources for a given test configuration are all declared in a single global res
Dependency resource IDs may be suffixed with `?` to indicate that the dependency is optional. Explicit constructor parameters must have a default value to accept optional dependencies. Dependencies accepted through `**kwargs` are treated as having an implicit default of `None`. Missing optional dependencies are omitted from the constructor call, so they do not appear in the `kwargs` dictionary.


## Resource-modifying resources
## Resource-providing resources

A `ResourceModifyingResource` is a resource capable of spawning other resources by modifying a template/base resource according to a desired key, such as an index. This is useful when a test scenario needs multiple unique-but-related instances of a resource (e.g., distinct flights derived from a single base flight).
A `ResourceProvidingResource[SpecificationType, ResourceType]` provides resources of `ResourceType` according to a desired key, such as an index. This is useful when a test scenario needs multiple related instances of a resource (e.g., spatially separated flights derived from a single base flight).

To use a `ResourceModifyingResource`:
To use a `ResourceProvidingResource`:

1. Declare it like any other resource, with its `base_resource` dependency pointing to the resource to be modified.
2. When a variant of the base/template resource is needed, call `modify(key)` to obtain a modified copy of the base resource. Different `key` values generally produce different variants; the same `key` should produce equivalent results.
1. Declare it like any other resource, with the dependencies its implementation requires.
2. Call `provide_resource_for` with the supported keys (e.g., `provide_resource_for(index=0)`).

The base/template resource itself remains available as `base_resource` on the modifier.
## Selecting a single provided resource

Use `resources.resource.SingularResource[ResourceType]` to place one indexed provider result in the resource pool:

```yaml
conflicting_flights:
resource_type: resources.resource.SingularResource[resources.flight_planning.FlightIntentsResource]
dependencies:
provider: conflicting_flights_provider
specification:
index: 0
```

The pool contains the actual resource returned by `provider.provide_resource_for(index=0)` rather than the SingularResource itself, so consumers can depend directly on `FlightIntentsResource`. The selected resource retains its own origin and generic type metadata. Providers must support an integer `index` key and return the declared resource type.

## Resolving into pools

Resource construction calls `resolve_resource()` after assigning generic type bindings. The default implementation returns `self`; selectors may override it to return the resource to expose to consumers. Resolution happens once during construction, and the result is reused by downstream dependencies.
49 changes: 48 additions & 1 deletion monitoring/uss_qualifier/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ def is_or_inherits(self, resource_type: ResourceTypeName) -> bool:
self._get_type_annotation(), specified_type, self._get_typevar_map()
)

def resolve_resource(self) -> "Resource":
"""Return the resource to expose in the resource pool after construction.

Selectors may return another resource. Its type metadata and origin are
preserved, and consumers receive that resource rather than the selector.
"""
return self


class ValueResource[T: ImplicitDict](Resource[T]):
value: T
Expand Down Expand Up @@ -142,6 +150,45 @@ def _provided_resource_origin(self, key_name: str) -> str:
return f"Resource for {key_name} provided by {self.resource_origin}"


class SingularResourceSpecification(ImplicitDict):
index: int
"""Index of the resource to select from the provider."""


class SingularResource[SingularResourceType: Resource](
Resource[SingularResourceSpecification]
):
"""Expose a single provider result as its actual resource type in the pool."""

_specification: SingularResourceSpecification
_provider: ResourceProvidingResource[ImplicitDict, SingularResourceType]

def __init__(
self,
specification: SingularResourceSpecification,
resource_origin: str,
provider: ResourceProvidingResource[ImplicitDict, SingularResourceType],
):
super().__init__(specification, resource_origin)
self._specification = specification
self._provider = provider

def resolve_resource(self) -> SingularResourceType:
resource = self._provider.provide_resource_for(index=self._specification.index)
expected_type = substitute_typevars(
get_type_hints(SingularResource.resolve_resource)["return"],
self._get_typevar_map(),
)
if not isinstance(resource, Resource) or not is_type_covariant_match(
resource._get_type_annotation(), expected_type, resource._get_typevar_map()
):
raise ValueError(
f"Resource provided by {self._provider.resource_origin} for index {self._specification.index} "
f"when resolving {self.resource_origin} does not match expected type {format_type_name(expected_type)}"
)
return resource


class MissingResourceError(ValueError):
missing_resource_name: str

Expand Down Expand Up @@ -399,7 +446,7 @@ def _make_resource(
**dependency_args,
)
resource._typevar_map = decl_types.typevar_map
return resource
return resource.resolve_resource()


def make_child_resources[ResourceType: Resource](
Expand Down
10 changes: 9 additions & 1 deletion monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ resources:
dss_instances: resources.astm.f3548.v21.DSSInstancesResource?
interuss_dss_instances: resources.astm.f3548.v21.DSSInstancesResource?
dss_datastore_cluster: resources.interuss.datastore.DatastoreDBClusterResource?
conflicting_flights: resources.flight_planning.FlightIntentsResource

# This input is DEPRECATED and may be removed in future versions.
conflicting_flights: resources.flight_planning.FlightIntentsResource?

# This resource should provide resources.flight_planning.FlightIntentsResource instances
conflicting_flights_provider: resources.resource.ResourceProvidingResource
Expand All @@ -28,6 +30,12 @@ resources:
system_identity: resources.versioning.SystemIdentityResource?
test_exclusions: resources.dev.TestExclusionsResource?
local_resources:
conflicting_flights:
resource_type: resources.resource.SingularResource[resources.flight_planning.FlightIntentsResource]
dependencies:
provider: conflicting_flights_provider
specification:
index: 0
system_identity:
resource_type: resources.versioning.SystemIdentityResource
specification:
Expand Down
7 changes: 5 additions & 2 deletions monitoring/uss_qualifier/suites/faa/uft/message_signing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ resources:
dss: resources.astm.f3548.v21.DSSInstanceResource
dss_instances: resources.astm.f3548.v21.DSSInstancesResource?
dss_datastore_cluster: resources.interuss.datastore.DatastoreDBClusterResource?
conflicting_flights: resources.flight_planning.FlightIntentsResource

# This input is DEPRECATED and may be removed in future versions.
conflicting_flights: resources.flight_planning.FlightIntentsResource?

non_conflicting_flights: resources.flight_planning.FlightIntentsResource

# This resource should provide resources.flight_planning.FlightIntentsResource instances
Expand All @@ -29,7 +32,7 @@ actions:
suite_type: suites.astm.utm.f3548_21
resources:
mock_uss: mock_uss
conflicting_flights: conflicting_flights
conflicting_flights: conflicting_flights?
conflicting_flights_provider: conflicting_flights_provider
non_conflicting_flights: non_conflicting_flights
priority_preemption_flights_provider: priority_preemption_flights_provider?
Expand Down
6 changes: 4 additions & 2 deletions monitoring/uss_qualifier/suites/uspace/flight_auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: U-space flight authorisation
resources:
test_env_version_providers: resources.versioning.VersionProvidersResource?
prod_env_version_providers: resources.versioning.VersionProvidersResource?
conflicting_flights: resources.flight_planning.FlightIntentsResource

# This input is DEPRECATED and may be removed in future versions.
conflicting_flights: resources.flight_planning.FlightIntentsResource?

# This resource should provide resources.flight_planning.FlightIntentsResource instances
conflicting_flights_provider: resources.resource.ResourceProvidingResource
Expand Down Expand Up @@ -30,7 +32,7 @@ actions:
resources:
test_env_version_providers: test_env_version_providers?
prod_env_version_providers: prod_env_version_providers?
conflicting_flights: conflicting_flights
conflicting_flights: conflicting_flights?
conflicting_flights_provider: conflicting_flights_provider
priority_preemption_flights_provider: priority_preemption_flights_provider?
invalid_flight_intents: invalid_flight_intents
Expand Down
5 changes: 3 additions & 2 deletions monitoring/uss_qualifier/suites/uspace/required_services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ resources:
test_env_version_providers: resources.versioning.VersionProvidersResource?
prod_env_version_providers: resources.versioning.VersionProvidersResource?

conflicting_flights: resources.flight_planning.FlightIntentsResource
# This input is DEPRECATED and may be removed in future versions.
conflicting_flights: resources.flight_planning.FlightIntentsResource?

# This resource should provide resources.flight_planning.FlightIntentsResource instances
conflicting_flights_provider: resources.resource.ResourceProvidingResource
Expand Down Expand Up @@ -51,7 +52,7 @@ actions:
resources:
test_env_version_providers: test_env_version_providers?
prod_env_version_providers: prod_env_version_providers?
conflicting_flights: conflicting_flights
conflicting_flights: conflicting_flights?
conflicting_flights_provider: conflicting_flights_provider
priority_preemption_flights_provider: priority_preemption_flights_provider?
invalid_flight_intents: invalid_flight_intents
Expand Down
8 changes: 5 additions & 3 deletions schemas/manage_type_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,15 @@ def _find_specifications(
):
_find_specifications(member, repo, already_checked)
elif inspect.isclass(member):
if getattr(member, "__parameters__", ()):
continue
if issubclass(member, Resource) and member != Resource:
if inspect.isabstract(member):
continue
spec_type = _resolve_resource_spec_type(member)
repo[fullname(spec_type)] = spec_type
# Generic resources can still have a concrete specification.
if isinstance(spec_type, type) and issubclass(spec_type, ImplicitDict):
repo[fullname(spec_type)] = spec_type
elif getattr(member, "__parameters__", ()):
continue
elif issubclass(member, ActionGenerator) and member != ActionGenerator:
spec_type = get_args(member.__orig_bases__[0])[0]
repo[fullname(spec_type)] = spec_type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/resources/resource/SingularResourceSpecification.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "monitoring.uss_qualifier.resources.resource.SingularResourceSpecification, as defined in monitoring/uss_qualifier/resources/resource.py",
"properties": {
"$ref": {
"description": "Path to content that replaces the $ref",
"type": "string"
},
"index": {
"description": "Index of the resource to select from the provider.",
"type": "integer"
}
},
"required": [
"index"
],
"type": "object"
}
Loading