diff --git a/monitoring/uss_qualifier/action_generators/__init__.py b/monitoring/uss_qualifier/action_generators/__init__.py index c738568938..e69de29bb2 100644 --- a/monitoring/uss_qualifier/action_generators/__init__.py +++ b/monitoring/uss_qualifier/action_generators/__init__.py @@ -1 +0,0 @@ -from .repeat import Repeat diff --git a/monitoring/uss_qualifier/action_generators/action_generator.py b/monitoring/uss_qualifier/action_generators/action_generator.py new file mode 100644 index 0000000000..71694d5547 --- /dev/null +++ b/monitoring/uss_qualifier/action_generators/action_generator.py @@ -0,0 +1,84 @@ +from __future__ import annotations +from abc import ABC, abstractmethod +import inspect +from typing import Generic, Dict, Optional, TypeVar + +from implicitdict import ImplicitDict +from monitoring import uss_qualifier as uss_qualifier_module +from monitoring.monitorlib.inspection import ( + import_submodules, + get_module_object_by_name, +) +from monitoring.uss_qualifier.action_generators.definitions import ( + ActionGeneratorSpecificationType, + ActionGeneratorDefinition, +) +from monitoring.uss_qualifier.reports.report import TestSuiteActionReport +from monitoring.uss_qualifier.resources.definitions import ResourceID +from monitoring.uss_qualifier.resources.resource import ResourceType + + +class ActionGenerator(ABC, Generic[ActionGeneratorSpecificationType]): + definition: ActionGeneratorDefinition + + @abstractmethod + def __init__( + self, + specification: ActionGeneratorSpecificationType, + resources: Dict[ResourceID, ResourceType], + ): + """Create an instance of the action generator. + + Concrete subclasses of ActionGenerator must implement their constructor according to this specification. + + :param specification: A serializable (subclass of implicitdict.ImplicitDict) specification for how to create the action generator. This parameter may be omitted if not needed. + :param resources: All of the resources available in the test suite in which the action generator is run. + """ + raise NotImplementedError( + "A concrete action generator type must implement __init__ method" + ) + + @abstractmethod + def run_next_action(self) -> Optional[TestSuiteActionReport]: + """Run the next action from the generator, or else return None if there are no more actions""" + raise NotImplementedError( + "A concrete action generator must implement `actions` method" + ) + + @staticmethod + def make_from_definition( + definition: ActionGeneratorDefinition, resources: Dict[ResourceID, ResourceType] + ) -> ActionGeneratorType: + from monitoring.uss_qualifier import ( + action_generators as action_generators_module, + ) + + import_submodules(action_generators_module) + action_generator_type = get_module_object_by_name( + parent_module=uss_qualifier_module, + object_name=definition.generator_type, + ) + if not issubclass(action_generator_type, ActionGenerator): + raise NotImplementedError( + "Action generator type {} is not a subclass of the ActionGenerator base class".format( + action_generator_type.__name__ + ) + ) + constructor_signature = inspect.signature(action_generator_type.__init__) + specification_type = None + constructor_args = {} + for arg_name, arg in constructor_signature.parameters.items(): + if arg_name == "specification": + specification_type = arg.annotation + break + if specification_type is not None: + constructor_args["specification"] = ImplicitDict.parse( + definition.specification, specification_type + ) + constructor_args["resources"] = resources + generator = action_generator_type(**constructor_args) + generator.definition = definition + return generator + + +ActionGeneratorType = TypeVar("ActionGeneratorType", bound=ActionGenerator) diff --git a/monitoring/uss_qualifier/action_generators/definitions.py b/monitoring/uss_qualifier/action_generators/definitions.py new file mode 100644 index 0000000000..8533a5ccc1 --- /dev/null +++ b/monitoring/uss_qualifier/action_generators/definitions.py @@ -0,0 +1,31 @@ +from typing import TypeVar, Dict + +from implicitdict import ImplicitDict +from monitoring.uss_qualifier.resources.definitions import ResourceID + + +GeneratorTypeName = str +"""This plain string represents a type of action generator, expressed as a Python class name qualified relative to the `uss_qualifier.action_generators` module""" + + +ActionGeneratorSpecificationType = TypeVar( + "ActionGeneratorSpecificationType", bound=ImplicitDict +) + + +class ActionGeneratorDefinition(ImplicitDict): + generator_type: GeneratorTypeName + """Type of action generator""" + + specification: dict = {} + """Specification of action generator; format is the ActionGeneratorSpecificationType that corresponds to the `generator_type`""" + + resources: Dict[ResourceID, ResourceID] + """Mapping of the ID a resource will be known by in the child action -> the ID a resource is known by in the parent test suite. + + The child action resource ID is supplied by the parent test suite resource ID . + + Resources not included in this field will not be available to the child action. + + If the parent resource ID is suffixed with ? then the resource will not be required (and will not be populated for the child action when not present in the parent) + """ diff --git a/monitoring/uss_qualifier/action_generators/repetition/__init__.py b/monitoring/uss_qualifier/action_generators/repetition/__init__.py new file mode 100644 index 0000000000..9c4c28af4a --- /dev/null +++ b/monitoring/uss_qualifier/action_generators/repetition/__init__.py @@ -0,0 +1 @@ +from monitoring.uss_qualifier.action_generators.repetition.repeat import Repeat diff --git a/monitoring/uss_qualifier/action_generators/repeat.py b/monitoring/uss_qualifier/action_generators/repetition/repeat.py similarity index 100% rename from monitoring/uss_qualifier/action_generators/repeat.py rename to monitoring/uss_qualifier/action_generators/repetition/repeat.py diff --git a/monitoring/uss_qualifier/configurations/dev/generate_rid_test_data.json b/monitoring/uss_qualifier/configurations/dev/generate_rid_test_data.json index 6930a925ef..d890bdf71e 100644 --- a/monitoring/uss_qualifier/configurations/dev/generate_rid_test_data.json +++ b/monitoring/uss_qualifier/configurations/dev/generate_rid_test_data.json @@ -9,7 +9,74 @@ }, "action": { "test_suite": { - "suite_type": "suites.interuss.generate_test_data_twice", + "suite_definition": { + "name": "Generate RID test data twice", + "resources": { + "adjacent_circular_flights_data": "resources.netrid.FlightDataResource", + "adjacent_circular_storage_config": "resources.netrid.FlightDataStorageResource", + "kml_flights_data": "resources.netrid.FlightDataResource", + "kml_storage_config": "resources.netrid.FlightDataStorageResource" + }, + "actions": [ + { + "action_generator": { + "generator_type": "action_generators.repetition.Repeat", + "specification": { + "action_to_repeat": { + "test_suite": { + "suite_definition": { + "name": "Generate RID test data", + "resources": { + "adjacent_circular_flights_data": "resources.netrid.FlightDataResource", + "adjacent_circular_storage_config": "resources.netrid.FlightDataStorageResource", + "kml_flights_data": "resources.netrid.FlightDataResource", + "kml_storage_config": "resources.netrid.FlightDataStorageResource" + }, + "actions": [ + { + "test_scenario": { + "scenario_type": "scenarios.astm.netrid.StoreFlightData", + "resources": { + "flights_data": "adjacent_circular_flights_data", + "storage_configuration": "adjacent_circular_storage_config" + } + }, + "on_failure": "Continue" + }, + { + "test_scenario": { + "scenario_type": "scenarios.astm.netrid.StoreFlightData", + "resources": { + "flights_data": "kml_flights_data", + "storage_configuration": "kml_storage_config" + } + }, + "on_failure": "Continue" + } + ] + }, + "resources": { + "adjacent_circular_flights_data": "adjacent_circular_flights_data", + "adjacent_circular_storage_config": "adjacent_circular_storage_config", + "kml_flights_data": "kml_flights_data", + "kml_storage_config": "kml_storage_config" + } + }, + "on_failure": "Abort" + }, + "times_to_repeat": 2 + }, + "resources": { + "adjacent_circular_flights_data": "adjacent_circular_flights_data", + "adjacent_circular_storage_config": "adjacent_circular_storage_config", + "kml_flights_data": "kml_flights_data", + "kml_storage_config": "kml_storage_config" + } + }, + "on_failure": "Continue" + } + ] + }, "resources": { "adjacent_circular_flights_data": "adjacent_circular_flights_data", "adjacent_circular_storage_config": "adjacent_circular_storage_config", diff --git a/monitoring/uss_qualifier/suites/definitions.py b/monitoring/uss_qualifier/suites/definitions.py index 6797e2d068..1e9299a009 100644 --- a/monitoring/uss_qualifier/suites/definitions.py +++ b/monitoring/uss_qualifier/suites/definitions.py @@ -1,8 +1,11 @@ from __future__ import annotations from enum import Enum -from typing import Dict, List, Optional, TypeVar +from typing import Dict, List, Optional from implicitdict import ImplicitDict +from monitoring.uss_qualifier.action_generators.definitions import ( + ActionGeneratorDefinition, +) from monitoring.uss_qualifier.fileio import load_dict_with_references, FileReference from monitoring.uss_qualifier.reports.capability_definitions import ( @@ -53,33 +56,6 @@ def type_name(self) -> str: return "" -GeneratorTypeName = str -"""This plain string represents a type of action generator, expressed as a Python class name qualified relative to the `uss_qualifier.action_generators` module""" - - -ActionGeneratorSpecificationType = TypeVar( - "ActionGeneratorSpecificationType", bound=ImplicitDict -) - - -class ActionGeneratorDefinition(ImplicitDict): - generator_type: GeneratorTypeName - """Type of action generator""" - - specification: dict = {} - """Specification of action generator; format is the ActionGeneratorSpecificationType that corresponds to the `generator_type`""" - - resources: Dict[ResourceID, ResourceID] - """Mapping of the ID a resource will be known by in the child action -> the ID a resource is known by in the parent test suite. - - The child action resource ID is supplied by the parent test suite resource ID . - - Resources not included in this field will not be available to the child action. - - If the parent resource ID is suffixed with ? then the resource will not be required (and will not be populated for the child action when not present in the parent) - """ - - class ReactionToFailure(str, Enum): Continue = "Continue" """If the test suite action fails, continue to the next action in that test suite""" @@ -168,7 +144,7 @@ class TestSuiteDefinition(ImplicitDict): @staticmethod def load_from_declaration( declaration: TestSuiteDeclaration, - ) -> "TestSuiteDefinition": + ) -> TestSuiteDefinition: if "suite_type" in declaration: return ImplicitDict.parse( load_dict_with_references(declaration.suite_type), TestSuiteDefinition diff --git a/monitoring/uss_qualifier/suites/interuss/generate_test_data.yaml b/monitoring/uss_qualifier/suites/interuss/generate_test_data.yaml deleted file mode 100644 index 4ac2c71589..0000000000 --- a/monitoring/uss_qualifier/suites/interuss/generate_test_data.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: Generate RID test data -resources: - adjacent_circular_flights_data: resources.netrid.FlightDataResource - adjacent_circular_storage_config: resources.netrid.FlightDataStorageResource - kml_flights_data: resources.netrid.FlightDataResource - kml_storage_config: resources.netrid.FlightDataStorageResource -actions: -- test_scenario: - scenario_type: scenarios.astm.netrid.StoreFlightData - resources: - flights_data: adjacent_circular_flights_data - storage_configuration: adjacent_circular_storage_config - on_failure: Continue -- test_scenario: - scenario_type: scenarios.astm.netrid.StoreFlightData - resources: - flights_data: kml_flights_data - storage_configuration: kml_storage_config - on_failure: Continue diff --git a/monitoring/uss_qualifier/suites/interuss/generate_test_data_twice.yaml b/monitoring/uss_qualifier/suites/interuss/generate_test_data_twice.yaml deleted file mode 100644 index 210f277b0b..0000000000 --- a/monitoring/uss_qualifier/suites/interuss/generate_test_data_twice.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: Generate RID test data twice -resources: - adjacent_circular_flights_data: resources.netrid.FlightDataResource - adjacent_circular_storage_config: resources.netrid.FlightDataStorageResource - kml_flights_data: resources.netrid.FlightDataResource - kml_storage_config: resources.netrid.FlightDataStorageResource -actions: -- action_generator: - generator_type: action_generators.Repeat - specification: - action_to_repeat: - test_suite: - suite_type: suites.interuss.generate_test_data - resources: - adjacent_circular_flights_data: adjacent_circular_flights_data - adjacent_circular_storage_config: adjacent_circular_storage_config - kml_flights_data: kml_flights_data - kml_storage_config: kml_storage_config - on_failure: Abort - times_to_repeat: 2 - resources: - adjacent_circular_flights_data: adjacent_circular_flights_data - adjacent_circular_storage_config: adjacent_circular_storage_config - kml_flights_data: kml_flights_data - kml_storage_config: kml_storage_config - on_failure: Continue diff --git a/monitoring/uss_qualifier/suites/suite.py b/monitoring/uss_qualifier/suites/suite.py index 0282ba26f7..c8e257560a 100644 --- a/monitoring/uss_qualifier/suites/suite.py +++ b/monitoring/uss_qualifier/suites/suite.py @@ -1,18 +1,16 @@ -from abc import ABC, abstractmethod +from __future__ import annotations from datetime import datetime -import inspect import json -from typing import Dict, List, Optional, TypeVar, Generic +from typing import Dict, List, Optional from implicitdict import StringBasedDateTime, ImplicitDict from loguru import logger import yaml -from monitoring import uss_qualifier as uss_qualifier_module -from monitoring.monitorlib.inspection import ( - fullname, - get_module_object_by_name, - import_submodules, +from monitoring.monitorlib.inspection import fullname +from monitoring.uss_qualifier.action_generators.action_generator import ( + ActionGeneratorType, + ActionGenerator, ) from monitoring.uss_qualifier.reports.capabilities import ( evaluate_condition_for_test_suite, @@ -42,8 +40,6 @@ TestSuiteActionDeclaration, TestSuiteDefinition, ReactionToFailure, - ActionGeneratorSpecificationType, - ActionGeneratorDefinition, ActionType, TestSuiteDeclaration, ) @@ -59,8 +55,8 @@ def _print_failed_check(failed_check: FailedCheck) -> None: class TestSuiteAction(object): declaration: TestSuiteActionDeclaration test_scenario: Optional[TestScenario] = None - test_suite: Optional["TestSuite"] = None - action_generator: Optional["ActionGeneratorType"] = None + test_suite: Optional[TestSuite] = None + action_generator: Optional[ActionGeneratorType] = None def __init__( self, @@ -277,69 +273,3 @@ def run(self) -> TestSuiteReport: ) return report - - -class ActionGenerator(ABC, Generic[ActionGeneratorSpecificationType]): - definition: ActionGeneratorDefinition - - @abstractmethod - def __init__( - self, - specification: ActionGeneratorSpecificationType, - resources: Dict[ResourceID, ResourceType], - ): - """Create an instance of the action generator. - - Concrete subclasses of ActionGenerator must implement their constructor according to this specification. - - :param specification: A serializable (subclass of implicitdict.ImplicitDict) specification for how to create the action generator. This parameter may be omitted if not needed. - :param resources: All of the resources available in the test suite in which the action generator is run. - """ - raise NotImplementedError( - "A concrete action generator type must implement __init__ method" - ) - - @abstractmethod - def run_next_action(self) -> Optional[TestSuiteActionReport]: - """Run the next action from the generator, or else return None if there are no more actions""" - raise NotImplementedError( - "A concrete action generator must implement `actions` method" - ) - - @staticmethod - def make_from_definition( - definition: ActionGeneratorDefinition, resources: Dict[ResourceID, ResourceType] - ) -> "ActionGeneratorType": - from monitoring.uss_qualifier import ( - action_generators as action_generators_module, - ) - - import_submodules(action_generators_module) - action_generator_type = get_module_object_by_name( - parent_module=uss_qualifier_module, - object_name=definition.generator_type, - ) - if not issubclass(action_generator_type, ActionGenerator): - raise NotImplementedError( - "Action generator type {} is not a subclass of the ActionGenerator base class".format( - action_generator_type.__name__ - ) - ) - constructor_signature = inspect.signature(action_generator_type.__init__) - specification_type = None - constructor_args = {} - for arg_name, arg in constructor_signature.parameters.items(): - if arg_name == "specification": - specification_type = arg.annotation - break - if specification_type is not None: - constructor_args["specification"] = ImplicitDict.parse( - definition.specification, specification_type - ) - constructor_args["resources"] = resources - generator = action_generator_type(**constructor_args) - generator.definition = definition - return generator - - -ActionGeneratorType = TypeVar("ActionGeneratorType", bound=ActionGenerator) diff --git a/schemas/monitoring/uss_qualifier/suites/definitions/ActionGeneratorDefinition.json b/schemas/monitoring/uss_qualifier/action_generators/definitions/ActionGeneratorDefinition.json similarity index 64% rename from schemas/monitoring/uss_qualifier/suites/definitions/ActionGeneratorDefinition.json rename to schemas/monitoring/uss_qualifier/action_generators/definitions/ActionGeneratorDefinition.json index 27a4877537..076034af97 100644 --- a/schemas/monitoring/uss_qualifier/suites/definitions/ActionGeneratorDefinition.json +++ b/schemas/monitoring/uss_qualifier/action_generators/definitions/ActionGeneratorDefinition.json @@ -1,37 +1,37 @@ { + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/action_generators/definitions/ActionGeneratorDefinition.json", "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", + "description": "monitoring.uss_qualifier.action_generators.definitions.ActionGeneratorDefinition, as defined in monitoring/uss_qualifier/action_generators/definitions.py", "properties": { "$ref": { - "type": "string", - "description": "Path to content that replaces the $ref" + "description": "Path to content that replaces the $ref", + "type": "string" }, "generator_type": { - "type": "string", - "description": "Type of action generator" + "description": "Type of action generator", + "type": "string" }, "resources": { - "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Mapping of the ID a resource will be known by in the child action -> the ID a resource is known by in the parent test suite.\n\nThe child action resource ID is supplied by the parent test suite resource ID .\n\nResources not included in this field will not be available to the child action.\n\nIf the parent resource ID is suffixed with ? then the resource will not be required (and will not be populated for the child action when not present in the parent)", "properties": { "$ref": { - "type": "string", - "description": "Path to content that replaces the $ref" + "description": "Path to content that replaces the $ref", + "type": "string" } }, - "additionalProperties": { - "type": "string" - }, - "description": "Mapping of the ID a resource will be known by in the child action -> the ID a resource is known by in the parent test suite.\n\nThe child action resource ID is supplied by the parent test suite resource ID .\n\nResources not included in this field will not be available to the child action.\n\nIf the parent resource ID is suffixed with ? then the resource will not be required (and will not be populated for the child action when not present in the parent)" + "type": "object" }, "specification": { - "type": "object", - "description": "Specification of action generator; format is the ActionGeneratorSpecificationType that corresponds to the `generator_type`" + "description": "Specification of action generator; format is the ActionGeneratorSpecificationType that corresponds to the `generator_type`", + "type": "object" } }, - "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/suites/definitions/ActionGeneratorDefinition.json", - "description": "monitoring.uss_qualifier.suites.definitions.ActionGeneratorDefinition, as defined in monitoring/uss_qualifier/suites/definitions.py", "required": [ "generator_type", "resources" - ] + ], + "type": "object" } \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/suites/definitions/TestSuiteActionDeclaration.json b/schemas/monitoring/uss_qualifier/suites/definitions/TestSuiteActionDeclaration.json index 28b77ba0ff..e2527d73fd 100644 --- a/schemas/monitoring/uss_qualifier/suites/definitions/TestSuiteActionDeclaration.json +++ b/schemas/monitoring/uss_qualifier/suites/definitions/TestSuiteActionDeclaration.json @@ -1,42 +1,33 @@ { + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/suites/definitions/TestSuiteActionDeclaration.json", "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", + "description": "Defines a step in the sequence of things to do for a test suite.\n\nExactly one of `test_scenario`, `test_suite`, or `action_generator` must be specified.\n\nmonitoring.uss_qualifier.suites.definitions.TestSuiteActionDeclaration, as defined in monitoring/uss_qualifier/suites/definitions.py", "properties": { "$ref": { - "type": "string", - "description": "Path to content that replaces the $ref" + "description": "Path to content that replaces the $ref", + "type": "string" }, "action_generator": { + "description": "If this field is populated, declaration of a generator that will produce 0 or more test suite actions", "oneOf": [ { "type": "null" }, { - "$ref": "ActionGeneratorDefinition.json" - } - ], - "description": "If this field is populated, declaration of a generator that will produce 0 or more test suite actions" - }, - "test_suite": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "TestSuiteDeclaration.json" + "$ref": "../../action_generators/definitions/ActionGeneratorDefinition.json" } - ], - "description": "If this field is populated, declaration of the test suite to run" + ] }, "on_failure": { - "type": "string", + "description": "What to do if this action fails", "enum": [ "Continue", "Abort" ], - "description": "What to do if this action fails" + "type": "string" }, "test_scenario": { + "description": "If this field is populated, declaration of the test scenario to run", "oneOf": [ { "type": "null" @@ -44,10 +35,19 @@ { "$ref": "../../scenarios/definitions/TestScenarioDeclaration.json" } - ], - "description": "If this field is populated, declaration of the test scenario to run" + ] + }, + "test_suite": { + "description": "If this field is populated, declaration of the test suite to run", + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "TestSuiteDeclaration.json" + } + ] } }, - "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/suites/definitions/TestSuiteActionDeclaration.json", - "description": "Defines a step in the sequence of things to do for a test suite.\n\nExactly one of `test_scenario`, `test_suite`, or `action_generator` must be specified.\n\nmonitoring.uss_qualifier.suites.definitions.TestSuiteActionDeclaration, as defined in monitoring/uss_qualifier/suites/definitions.py" + "type": "object" } \ No newline at end of file