Generic mechanisms API (TMC Monolith)
Here below are documented the generic mechanisms of the ska-integration-test-harness. Implementation of specific actions and structures are currently left outside, see directly the code for more details.
IMPORTANT NOTE: A very crucial aspect of the generic mechanisms is that they are unit tested code, while the specific actions and structures are not unit tested (and in a certain sense, the integration tests that use the test harness are the validation of the correctness of the implementation of the specific actions and structures). The coverage metrics of the test harness are calculated only on the generic mechanisms (at the time of writing, the coverage on the generic mechanisms is approximately 90%, while the overall coverage is around 60%).
Structure module
A collection of wrappers for various system components.
TelescopeWrapper
is the main entry point for the telescope system.
Right now, it exposes the following subsystems (through a wrapper for each):
TMC
SDP
CSP
Dishes
All the subsystem wrappers are represented by abstract classes, so you can create your own implementation for each one (e.g., an implementation that points to the devices of a real subsystem, or an implementation that points to the devices of an emulated subsystem).
The module also contains a generic
SubsystemWrapper
class that can be used as a base class for any subsystem wrapper.
- class ska_integration_test_harness.structure.CSPWrapper(csp_configuration)
A test wrapper for the CSP.
- get_all_devices()
Get all the subsystem devices as a dictionary.
- Return type:
- class ska_integration_test_harness.structure.DishesWrapper(dishes_configuration)
A test wrapper for the dishes.
- property dish_master_list: list[tango.DeviceProxy]
Dish Master device proxies as a list (sorted by key).
- get_all_devices()
Get all the subsystem devices as a dictionary.
- Return type:
- class ska_integration_test_harness.structure.SDPWrapper(sdp_configuration)
A test wrapper for the SDP.
- get_all_devices()
Get all the subsystem devices as a dictionary.
- Return type:
- class ska_integration_test_harness.structure.SubsystemWrapper
The abstract definition of a subsystem wrapper.
A subsystem is a part of the telescope involved in the tests (e.g. CSP, TMC, Dishes, etc.). A subsystem has the following properties:
has a name
has Tango devices
can be emulated or not
On a subsystem, we can perform the following operations:
access the properties described above
get a recap of the subsystem information (system name, emulated or not, devices information)
This class represents the abstract definition of a subsystem wrapper. Extend this class and implement the abstract methods to create a subsystem wrapper for a specific subsystem. Your child class may be also abstract, if you want for example to distinguish between emulated and production subsystems.
- abstract get_all_devices()
Get all the subsystem devices as a dictionary.
- Return type:
- Returns:
A dictionary of device proxies, where the key is an unique identifier of the device and the value is the device proxy.
NOTE: the unique identifier is not necessarily the device name, but more something that explains “what role” the device has in the subsystem (e.g., “central_node”, “dish_1”, etc.).
- get_recap(devices_info_provider=None)
Get a (string) recap of the subsystem information.
The recap contains the subsystem name, the emulated status and the devices information. The devices information is a list of the devices in the subsystem, eventually enriched with further information (e.g., version, etc.).
- Parameters:
devices_info_provider (
Optional[DevicesInfoProvider]) – The provider to get info about Tango devices from ska-k8s-config-exporter. If None, only the device names are included in the recap.- Return type:
- abstract get_subsystem_name()
Get the name of the subsystem.
- Return type:
- Returns:
The name of the subsystem.
- class ska_integration_test_harness.structure.TMCWrapper(tmc_configuration)
A wrapper for the TMC component.
- get_all_devices()
Get all the subsystem devices as a dictionary.
- Return type:
- get_recap(devices_info_provider=None)
Get a (string) recap of the subsystem information.
The recap contains the subsystem name, the emulated status and the devices information. The devices information is a list of the devices in the subsystem, eventually enriched with further information (e.g., version, etc.).
- Parameters:
devices_info_provider (
Optional[DevicesInfoProvider]) – The provider to get info about Tango devices from ska-k8s-config-exporter. If None, only the device names are included in the recap.- Return type:
- set_subarray_id(subarray_id)
Set subarray ID
- class ska_integration_test_harness.structure.TelescopeWrapper
A wrapper class that contains all the telescope subsystems.
This infrastructural class is used as an unique entry point to access all the telescope subsystems and its devices. Given an instance of this class, using its properties, it is possible to access the TMC, SDP, CSP, and Dishes subsystems.
This class is a Singleton, so this mean that there is only one instance of it in the entire code. This is done to avoid the creation of multiple “telescope” instances, potentially inconsistently initialised with different subsystem instances (which may be configured differently).
To initialise the telescope test structure, create an instance of this class and call the set_up method with the instances of the TMC, SDP, CSP, and Dishes subsystems. After the initialisation, in any point of the code you can create an instance of this class and have it already initialised with the subsystems.
# Initialise the telescope test structure telescope = TelescopeWrapper() telescope.set_up(tmc, sdp, csp, dishes) # ... # In any point of the code, you have access to the subsystems # (and their devices) using the properties of the telescope instance. telescope = TelescopeWrapper() do_something(telescope.tmc.central_node)
When you are done with testing, you can tear down the entire telescope test structure and reset it to the initial state calling the tear_down method.
# Tear down the telescope test structure telescope = TelescopeWrapper() telescope.tear_down()
The Singleton is a pretty much standard design pattern. To learn more about it, you can refer to the following resources.
General idea explanation: https://refactoring.guru/design-patterns/singleton
Python implementation: https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/
- clear_command_call()
Clear the command call on the telescope (if needed).
- Raises:
ValueError – If one or more subsystems are missing.
- Return type:
- property csp: CSPWrapper
A wrapper for the CSP subsystem and its devices.
- Raises:
ValueError – If one or more subsystems are missing.
-
devices_info_provider:
DevicesInfoProvider|None= None The devices info provider used to access the devices information.
(Used for recap purposes).
- property dishes: DishesWrapper
A wrapper for the dishes subsystem and its devices.
- Returns:
The DishesDevices instance.
- Raises:
ValueError – If one or more subsystems are missing.
- fail_if_not_set_up()
Fail if a valid structure is not set up.
- Raises:
ValueError – If one or more subsystems are missing.
- Return type:
- get_active_subsystems()
Get all the active subsystems.
- Return type:
- Returns:
The list of all the subsystems.
- get_required_subsystems()
Get all the required subsystems (based on the TMC configuration).
- Return type:
- Returns:
The list of all the required subsystems.
- get_subsystems_recap(update_devices_info=True)
Get a recap of the active subsystems and their devices.
Get a recap of the active subsystems, their production-emulated status, and the devices they contain.
- property mccs: MCCSWrapper
A wrapper for the MCCS subsystem and its devices.
- Returns:
The MCCSDevices instance.
- Raises:
ValueError – If one or more subsystems are missing.
- property sdp: SDPWrapper
A wrapper for the SDP subsystem and its devices.
- Raises:
ValueError – If one or more subsystems are missing.
- set_subarray_id(subarray_id)
Create subarray devices for the requested subarray.
- Parameters:
subarray_id (
int) – The Subarray ID to set.- Raises:
ValueError – If one or more subsystems are missing.
- Return type:
- set_up(tmc, sdp, csp, dishes=None, mccs=None)
Initialise the telescope test structure with the given devices.
- Parameters:
tmc (
TMCWrapper) – The TMC subsystem wrapper.sdp (
SDPWrapper) – The SDP subsystem wrapper.csp (
CSPWrapper) – The CSP subsystem wrapper.dishes (
Optional[DishesWrapper]) – The Dishes subsystem wrapper (required for mid).mccs (
Optional[MCCSWrapper]) – The MCCS subsystem wrapper (required for low).
- Return type:
- tear_down()
Tear down the entire telescope test structure.
- Raises:
ValueError – If one or more subsystems are missing.
- Return type:
- property tmc: TMCWrapper
A wrapper for the TMC subsystem and its devices.
- Returns:
The TMCDevices instance.
- Raises:
ValueError – If one or more subsystems are missing.
Actions module
A structure for performing actions on the telescope system.
This module contains the structure for implementing actions on the telescope.
An action is essentially an abstraction of a procedure that can be executed
on the telescope system and - essentially - it is made by a business logic
and a termination condition to synchronise the execution. Actions then can be
represented through a hierarchy of classes, which have as a root the
TelescopeAction.
The main mechanism is the one provided by the
ska_tango_testing.integration.tracer.TangoEventTracer that allows
the implementation of a
StateChangeWaiter
to wait for a specific state change in the system and so to synchronise
after an action is executed.
Further details in the classes documentation.
- class ska_integration_test_harness.actions.StateChangeWaiter
A tool to wait for a set of state changes from multiple Tango devices.
This class is used to wait for a set of state changes to occur on multiple Tango devices. You use this class by creating an instance of it, progressively adding state changes to wait for, and then calling the wait_all method to wait for all the state changes to occur (or for a timeout to occur). If all the state changes occur before the timeout, the execution continues, else it will be raised an exception.
An instance of this class can be shared among multiple classes, so you can separate the responsibility of knowing what conditions to wait foreach group of devices (e.g., a TMC class may know what to wait for the TMC devices, and a CSP class may know what to wait for the CSP devices, etc.) and also move the wait_all call in a common orchestrator class (that doesn’t need to know the details of what to wait for).
Calling the method reset you will clear the list of expected state changes, so you can reuse the same instance for multiple actions.
- add_expected_state_changes(state_changes)
Add a list of expected state changes to wait for.
- reset()
Clear the list of expected state changes.
- wait_all(timeout)
Wait for all the expected state changes to occur.
- Parameters:
timeout (
int|float) – The maximum time (in seconds) to wait for the state changes.- Raises:
TimeoutError – If not all the expected state changes occurred within the timeout.
- Return type:
- class ska_integration_test_harness.actions.TelescopeAction
A generic action executed on the telescope and its subsystems.
An action is made by:
the action itself, which is the procedure that interacts with telescope subsystems (TMC, CSP, SDP, Dishes);
a termination condition, which is a set of expected events that should occur after the action is executed and which define a successful completion of the action;
a return type T, which is the type of the expected result of the action.
This class is a template for such actions.
SUBCLASS AN ACTION
To create a new action, you create a subclass of
TelescopeActionand you implement the abstract methods_action()andtermination_condition(). You define your business logic in the_action()method and you define the termination condition in thetermination_condition()method, as a list oftests.test_harness3.telescope_actions.expected_events.ExpectedEventinstances (or subclasses).Here a few guidelines about the usage.
If your action needs some parameters, you can override the
__init__()method to accept them and store them as instance attributes.When you implement an action you have to specify a return type. If your action doesn’t return anything, you can specify
Noneas return type.Even if the action has no termination condition, you should still specify that by returning an empty list of expected events in the
termination_condition()method.If in your action you need to access the telescope instance, you can do it by using the attribute
self.telescope. If you need to log something (only if the logging policy is active), you can use the method_log(). If for some reason you need, you can also access the internal configuration and the components.
Usage example:
from tango import DevState from ska_integration_test_harness.actions.telescope_action import ( TelescopeAction ) from ska_integration_test_harness.actions.expected_events import ( ExpectedStateChange ) # create an action that returns nothing # and has no termination condition. It takes a parameter through. class MyAction(TelescopeAction[None]): def __init__(self, my_parameter: int): super().__init__() self.my_parameter = my_parameter def _action(self): # your business logic here pass def termination_condition(self): return [] # create an action that - if necessary - runs # a certain Tango command on TMC central node and terminates # when that device reaches a certain state. The action then # returns true or false depending if the command was necessary or not. # Some further logging is done if the command wasn't necessary. class CentralNodeMoveToOn(TelescopeAction[bool]): def _action(self): if self.telescope.tmc.central_node.State != DevState.ON: self.telescope.tmc.central_node.On() return True self._log( "Central node is already ON. No need to run the command." ) return False def termination_condition(self): return [ ExpectedStateChange( device=self.telescope.tmc.central_node, attribute="State", expected_value=DevState.ON, ) ]
EXECUTE AN ACTION
To run an action, you call the method
execute(). By default, when called the method:the action is executed;
the termination condition is waited for until it occurs;
some logging is performed to report the execution beginning and end;
if the termination condition does not occur within a timeout, a TimeoutError is raised.
Before calling the
execute()method, you can customise some configurations of the action:you can change the timeout for the termination condition by calling the method
set_termination_condition_timeout()and passing the new timeout;you can execute the action without waiting for the termination condition by calling the method
set_termination_condition_policy()and passingFalseas argument;you can deactivate the logging policy by calling the method
set_logging_policy()and passingFalseas argument.
NOTE: An already configured action can be executed multiple times.
Usage example:
# create an instance of the action action = MyAction(my_parameter=42) # [optional] customise the timeout for the termination condition action.set_termination_condition_timeout(10) # [optional] execute the action without waiting for the # termination condition to occur action.set_termination_condition_policy(False) # [optional] deactivate the logging policy action.set_logging_policy(False) # execute the action action.execute() # execute the action again action.execute()
RATIONALE AND INSPIRATIONS
This class is strongly inspired by the Command design pattern (https://refactoring.guru/design-patterns/command), since it abstracts a potentially complex action into a class, incapsulating and hiding the execution details of the action itself, such as having available a telescope instance and waiting for the termination condition to occur. Since it leaves you some methods to implement (that will instead be called by this class itself), it is also inspired by the Template Method design pattern (https://refactoring.guru/design-patterns/template-method).
- do_logging
If True, the action will log its execution beginning and end.
- execute()
Execute the action.
By default, when called the method: :rtype:
Optional[Any]the action is executed;
the termination condition is waited for until it occurs;
some logging is performed to report the execution beginning and end;
if the termination condition does not occur within a timeout, a TimeoutError is raised.
Before calling the
execute()method, you can customise some configurations of the action:you can change the timeout for the termination condition by calling the method
set_termination_condition_timeout()and passing the new timeout;you can execute the action without waiting for the termination condition by calling the method
set_termination_condition_policy()and passingFalseas argument;you can deactivate the logging policy by calling the method
set_logging_policy()and passingFalseas argument.
NOTE: An already configured action can be executed multiple times.
Usage example:
# create an instance of the action action = MyAction(my_parameter=42) # [optional] customise the timeout for the termination condition action.set_termination_condition_timeout(10) # [optional] execute the action without waiting for the # termination condition to occur action.set_termination_condition_policy(False) # [optional] deactivate the logging policy action.set_logging_policy(False) # execute the action action.execute() # execute the action again action.execute()
- Raises:
TimeoutError – If the expected outcome does not occur within a timeout.
- get_last_execution_result()
Get the result of the last execution of the action (if any).
- set_logging_policy(do_logging)
Change the policy for logging the action.
If you call this method with
do_logging=False, the action will not log its execution beginning and end. If you call this method withdo_logging=True, the action will log its execution beginning and end.
- set_termination_condition_policy(wait)
Change the policy for waiting for the termination condition.
If you call this method with
wait=False, the termination condition will not be waited for when calling theexecute()method. If you call this method withwait=True, the termination condition will be waited for when calling theexecute()method.
- set_termination_condition_timeout(timeout)
Change the timeout for the termination condition.
The timeout is the maximum time to wait for the termination condition to occur. You can change it at any time before calling the
execute()method.
- telescope
The telescope instance, which you can use to access all the subsystem devices (TMC, CSP, SDP, Dishes).
- abstract termination_condition()
The termination condition of the action.
The termination condition is a list of expected events. Override this method to define the expected events that should occur. Remember that by default this method is called BEFORE the action is executed, so you can access the devices states and attributes and store some useful information to define the termination condition.
Also remember that the termination condition is being waited AFTER the action procedure is executed. So, you can define a termination condition that is based on the action result (through the use opportune lambda functions based on the call of the action result getter
get_last_execution_result).- Return type:
list[ExpectedEvent]- Returns:
A list of expected events that define the termination condition of the action. They should be instances of
tests.test_harness3.telescope_actions.expected_events.ExpectedEventor subclasses, like the very usefultests.test_harness3.telescope_actions.expected_events.ExpectedStateChange.
- class ska_integration_test_harness.actions.TelescopeActionSequence(steps)
A sequence of TelescopeAction, executed in order.
This action is used to group a sequence of actions together, so that the can be executed as a single action. The sub-actions are executed in the order they are provided and the synchronisation is done after each sub action (step). By default, this action does not have further termination conditions.
By default, each step keeps the default wait termination condition timeout. Calling the
set_termination_condition_timeout(timeout)method you will apply the change to each of the steps.By default, the termination condition policy is set to wait for the termination condition of each of the steps. Calling
set_termination_condition_policy(False)you will make the last step to not wait for the termination condition (but all the others will still keep their previous policy). Callingset_termination_condition_policy(True)you will make all the steps to wait for the termination condition. If you need, you can set the termination condition policy for each step by calling the method on each of them (you can access them throughsteps).Usage example:
# Create a sequence of actions sequence = TelescopeActionSequence([ action1, action2, action3, ]) # Execute the sequence result = sequence.execute() # (you can always access the steps, set policies, etc.) sequence.steps[0].set_termination_condition_timeout(10) sequence.set_logging_policy(True) # ...
- set_logging_policy(do_logging)
Propagate a new logging policy to each of the steps.
- set_termination_condition_policy(wait)
Set the termination condition policy for the steps as follows.
set to True: all the steps will wait for the termination condition.
- set to False: the last step will not wait for the
termination condition.
- set_termination_condition_timeout(timeout)
Propagate a new timeout value to each of the steps.
- termination_condition()
The sequence by itself does not have a termination condition.
The termination condition is handled by the steps. If configured, the termination condition of the last step will be used.
- Returns:
An empty list.
- class ska_integration_test_harness.actions.TelescopeCommandAction(target_device=None, is_long_running_command=False)
An action that send a command to some telescope subsystem.
Such an action:
may have a target encoded in the action, which can be a Tango device
may be a LRC (long running command) or not
returns as a tuple the command result
If the command is a LRC (and a target device is provided), by default the action will wait for the LRC to terminate. If you want to extend the termination condition to wait for more things or not to wait for the LRC to terminate, you can override the
termination_conditionmethod (and include or not the result of the superclass method in your own returned list). Actually, just changing theis_long_running_commandattribute toFalseyou can avoid waiting for the LRC to terminate.The result of the command is a tuple with two elements:
the first element is the result of the command (e.g., ResultCode.OK)
the second element is one or more messages about the command execution.
If the command is a LRC, the second element can be used as a reference to check the command status through the longRunningCommandResult attribute. In fact, the waiting condition for the LRC termination is based on this attribute.
IMPORTANT NOTE: at the moment, the command itself is not part of this action. This is because I don’t yet want to interfere with the way you call it (e.g., with the command name, the command input, etc.). This may be subject to change in the future, but for now, you have to implement the command execution in the
_actionmethod as you would do in a normal action.- is_long_running_command
Whether the command is a long running command or not.
- target_device
The Tango device to which the command will be sent.
- termination_condition()
Wait for the LRC to terminate.
If the command is a LRC (and a target device is provided), by default the action will wait for the LRC to terminate. If you want to extend the termination condition to wait for more things or not to wait for the LRC to terminate, you can override the
termination_conditionmethod (and include or not the result of the superclass method in your own returned list).- Return type:
list[ExpectedEvent]- Returns:
A list of ExpectedEvent objects to wait for (which is empty by default, or includes the termination condition for the LRC if the command is marked as a LRC).
- termination_condition_for_lrc()
Wait for the LRC to terminate.
Implement this method to specify the termination condition for when the command is a long running command.
- Return type:
list[ExpectedEvent]- Returns:
A list of ExpectedEvent objects to wait for, when the command is a long running command.
- class ska_integration_test_harness.actions.TransientQuiescentCommandAction(target_device=None, is_long_running_command=False, synchronise_on_transient_state=False)
A command which can synchronise on a quiescent or on a transient state.
This class represents a command that when called can be synchronised on a quiescent state (the final expected state) or on a transient state (an intermediate state in the command execution). Concretely, this means that the action has two instead of one termination conditions: one for the quiescent state and one for the transient state. By default, the action will synchronise on the quiescent state. If you want to synchronise on the transient state, you can set the
synchronise_on_transient_stateattribute to True.This is a subclass of
TelescopeCommandAction, so it still inherits the fact of having a target device and being a LRC or not. If this action is set as a LRC, when you synchronise on the quiescent state, the action termination condition by default will include the LRC termination condition.- set_synchronise_on_transient_state(value)
Set the synchronise_on_transient_state attribute.
- Parameters:
value (
bool) – If True, the action will synchronise on the next transient state instead of the next quiescent state. By default, it is False, so the action will synchronise on the next quiescent state.
- synchronise_on_transient_state
If True, the action will synchronise on the next transient state instead of the next quiescent state. By default, it is False, so the action will synchronise on the next quiescent state.
- termination_condition()
Wait for the quiescent or transient state of the subarray.
By default, the action will synchronise on the next quiescent state, but if the synchronise_on_transient_state attribute is True, it will synchronise on the next transient state. Eventual inherited termination conditions are also include when synchronising on the quiescent state.
- Return type:
list[ExpectedEvent]- Returns:
A list of ExpectedEvent objects to wait for.
- abstract termination_condition_for_quiescent_state()
Wait for the quiescent state of the subarray.
Implement this method to specify the termination condition for when you want to synchronise on the quiescent state.
- Return type:
list[ExpectedEvent]- Returns:
A list of ExpectedEvent objects to wait for, when you want to synchronise on the quiescent state.
- abstract termination_condition_for_transient_state()
Wait for the transient state of the subarray.
Implement this method to specify the termination condition for when you want to synchronise on the transient state.
- Return type:
list[ExpectedEvent]- Returns:
A list of ExpectedEvent objects to wait for, when you want to synchronise on the transient state.
Inputs module
A collection of (mostly abstract) classes to represent the required inputs.
This module contains classes that represent the required inputs for the telescope actions. Mainly, those inputs come in the form of JSON strings, for which we provide an abstraction to represent them.
Currently, it contains also some classes (left outside from the documentation on purpose) to represent various data types that - in theory - they should belong to external repositories but that for some reason they are here.
- class ska_integration_test_harness.inputs.DictJSONInput(json_dict)
A JSON input that is represented as a dictionary.
This class is a concrete implementation of the JSONInput abstract class which uses a dictionary to represent the JSON input. This class is useful when the JSON input you want to provide is already a dictionary.
Since a dictionary can be always converted to a valid JSON string, this class is guaranteed to always return a valid JSON string representation. Since the representation is valid,
as_dictandwith_attributewill never raise an exception.
- class ska_integration_test_harness.inputs.FileJSONInput(json_file, encoding='utf-8')
A JSON input that is represented as a file path.
This class is a concrete implementation of the JSONInput abstract class which uses a file path to represent the JSON input. This class is useful when the JSON input is already a file path and you don’t want to necessarily parse it to a dictionary.
IMPORTANT NOTE: The string representation of the JSON input is not guaranteed to be a valid JSON string. This means that the
as_dictandwith_attributemethods may raise an exception if the JSON string is invalid. However, theas_strmethod should always return a string.OTHER IMPORTANT NOTE: Since the file path is used to represent the JSON input, this class constructor may raise the exceptions you would expect when working with files (e.g., FileNotFoundError, PermissionError).
- class ska_integration_test_harness.inputs.JSONInput
Template for a generic JSON input.
This class is an abstract class that defines a template for a JSON input for a command on the telescope. It is meant to be used as a base class to implement your own way to provide that JSON value.
IMPORTANT NOTE: This class instances are meant to be immutable. If you need to change the value of the JSON input, you should create a new instance with the new value (see the with_attribute method).
- abstract as_dict()
Return the JSON dictionary representation of the input.
This representation is guaranteed to be a valid JSON dictionary, but it may raise an exception if this instance represents an invalid JSON string.
- Return type:
- Returns:
The JSON dictionary representation of the input.
- Raises:
json.JSONDecodeError – if the JSON string is invalid.
- abstract as_str()
Return the JSON string representation of the input.
This representation is not guaranteed to be valid JSON, but it should always be guaranteed to be returned as a string without raising any exceptions.
- Return type:
- Returns:
The JSON string representation of the input.
- is_equal_to_json(json_data)
Check if this input is equal to the provided JSON data.
- Return type:
- abstract with_attribute(attr_name, attr_value)
Decorate the input with an additional attribute.
This method should return a new instance of the JSON input with the specified attribute set to the specified value.
This representation is guaranteed to be a valid JSON dictionary, but it may raise an exception if this instance represents an invalid JSON string.
- Parameters:
- Raises:
json.JSONDecodeError – if the JSON string is invalid.
- Return type:
- class ska_integration_test_harness.inputs.StrJSONInput(json_str)
A JSON input that is represented as a string.
This class is a concrete implementation of the JSONInput abstract class which uses a string to represent the JSON input. This class is useful when the JSON input is already a string and you don’t want to necessarily parse it to a dictionary.
IMPORTANT NOTE: The string representation of the JSON input is not guaranteed to be a valid JSON string. This means that the
as_dictandwith_attributemethods may raise an exception if the JSON string is invalid. However, theas_strmethod should always return a string.
- class ska_integration_test_harness.inputs.TestHarnessInputs(default_vcc_config_input=None, assign_input=None, configure_input=None, scan_input=None, release_input=None)
A collection of inputs for the test harness.
This data class is a collection of inputs that may be needed for some processes in the test harness. For example, it can be used to specify the inputs for the subarray observation state resetter, or also the default inputs injected in the test harness to perform various operations (such as the teardown).
The contained inputs are:
a default VCC configuration as a JSON, needed for the teardown procedure,
a set of JSON inputs for the obsState state machine: - assign - configure - scan - release
A support enum is provided to specify the input names, and allow access to the inputs in a parametric way.
- class InputName(value)
An enumeration of the possible input names.
An enumeration of the possible input names that could be included as test harness inputs.
- ASSIGN = 'assign'
- CONFIGURE = 'configure'
- DEFAULT_VCC_CONFIG = 'default_vcc_config'
- RELEASE = 'release'
- SCAN = 'scan'
- get_input(input_name, fail_if_missing=False)
Get the input with the given name.
- Parameters:
- Return type:
- Returns:
The input with the given name.
- Raises:
ValueError – If the input is missing and fail_if_missing is set to True.
- get_non_none_json_inputs()
Get the non-None JSON inputs as a dictionary.
Inputs validation
Tools to validate the inputs to the test harness.
- class ska_integration_test_harness.inputs.validation.BasicInputValidator(logger=None)
A basic validator for the input data.
For how it is implemented now:
it checks the presence of the inputs for: default VCC config, assign, configure, scan, release;
it checks each of those input is a valid JSON.
Instead, it doesn’t check the semantic correctness of the JSONs.
- additional_inputs_for_mid
The list of the additional inputs required for the MID.
- required_inputs
The list of the required inputs.
- validate_inputs_correctness(inputs)
Ensure all the passed inputs are valid JSONs.
- Return type:
- class ska_integration_test_harness.inputs.validation.InputValidator(logger=None)
A validator for the input data.
It provides interfaces for two main validation steps:
a step to validate the presence of a needed set of inputs
a step to validate each input individually
It provides also a minimal log.info utility to log the validation steps.
- logger
An optional logger used to log the errors and warnings, while they are found during the validation.
- logger_prefix
The prefix used to log the validation messages.
- abstract validate_inputs_correctness(inputs)
Check the correctness of the given inputs.
Validate all inputs using the configured validation rules. If any critical error is found, a ValueError is raised.
- Raises:
ValueError – If any critical error is found.
- Return type:
- abstract validate_inputs_presence(inputs, is_mid=True)
Validate the presence of the required inputs.
A given set of inputs must contain all the needed inputs to run the tests.
If the logger is set, all the errors and warnings are logged.
- Raises:
ValueError – If any critical error is found.
- Return type:
Config(uration) module
Test harness configurations.
This module contains classes that represent, read and validate the configuration files for the test harness.
- class ska_integration_test_harness.config.CSPConfiguration(is_emulated=True, target='mid', csp_master_name=None, csp_subarrays_names=<factory>, pst_name=None)
Configuration for a CSP device.
It contains the names of the various devices that make up the CSP. It is initialised with default values.
- get_device_names()
Return all the device names.
(associated with they “keyword” name in the configuration)
- mandatory_attributes()
Return the names of the mandatory attributes.
- class ska_integration_test_harness.config.DishesConfiguration(is_emulated=True, target='mid', dish_master1_name=None, dish_master2_name=None, dish_master3_name=None, dish_master4_name=None)
Configuration for the dishes.
It contains the names of the various dishes. It is initialised with default values (which are the ones you will have when the dishes are emulated).
- get_device_names()
Return all the device names.
(associated with they “keyword” name in the configuration)
- class ska_integration_test_harness.config.SDPConfiguration(is_emulated=True, target='mid', sdp_master_name=None, sdp_subarrays_names=<factory>)
Configuration for a SDP device.
It contains the names of the various devices that make up the SDP. It is initialised with default values.
- get_device_names()
Return all the device names.
(associated with they “keyword” name in the configuration)
- mandatory_attributes()
Return the names of the mandatory attributes.
- class ska_integration_test_harness.config.SubsystemConfiguration(is_emulated=True, target='mid')
A generic configuration for a telescope subsystem.
A subsystem is a collection of devices that are logically grouped together, such as CSP, SDP, TMC and the Dishes. This class provides a way to encapsulate the names of the devices that make up the subsystem.
To use this class:
extend it,
add as attributes your own configuration parameters,
implement a few methods to expose attributes containing device names and the mandatory attributes.
A subsystem in the context of SKA can be emulated or a production one. This class contains a boolean flag that will specify that. By default, all configurations point to emulated devices. You don’t need to list the
is_emulatedattribute in theall_attributes- abstract get_device_names()
Return all the device names.
(associated with they “keyword” name in the configuration)
- abstract mandatory_attributes()
Return the names of the mandatory attributes.
- class ska_integration_test_harness.config.TMCConfiguration(is_emulated=True, target='mid', centralnode_name=None, tmc_csp_master_leaf_node_name=None, tmc_sdp_master_leaf_node_name=None, tmc_mccs_master_leaf_node_name=None, subarrays_names=<factory>, tmc_csp_subarrays_leaf_nodes_names=<factory>, tmc_sdp_subarrays_leaf_nodes_names=<factory>, tmc_mccs_subarrays_leaf_nodes_names=<factory>, tmc_dish_leaf_node1_name=None, tmc_dish_leaf_node2_name=None, tmc_dish_leaf_node3_name=None, tmc_dish_leaf_node4_name=None)
Configuration for a TMC device.
It contains the names of the various devices that make up the TMC. It is initialised with default values.
- get_device_names()
Return all the device names.
(associated with they “keyword” name in the configuration)
- mandatory_attributes()
Return the names of the mandatory attributes.
- class ska_integration_test_harness.config.TestHarnessConfiguration(tmc_config=None, csp_config=None, sdp_config=None, dishes_config=None, mccs_config=None)
A wrapper to all the configurations needed to setup the test harness.
This class is a wrapper to all the configurations needed to setup the test harness. It is used to pass all the configurations to the test harness factory, so it can create the needed test harness wrappers.
Each field of this class is a configuration for a different subsystem of the test harness, such as the TMC, CSP, SDP, and the dishes. Even if they are marked as optional, for now they are all required, as the test harness is strictly built for TMC-Mid integration tests. In the future, we may support an elastic choice of which subsystems to include, so this configuration may be useful to select only the needed subsystems.
A support enum is provided to specify the subsystem names, and allow access to the configurations in a parametric way.
- class SubsystemName(value)
An enumeration of the possible subsystem names.
An enumeration of the possible subsystems that could be included in the the test harness configuration.
- CSP = 'csp'
- DISHES = 'dishes'
- MCCS = 'mccs'
- SDP = 'sdp'
- TMC = 'tmc'
- all_emulated()
Check if, among the included subsystems, all are emulated.
- Return type:
- Returns:
True if all the subsystems are emulated, False otherwise.
- all_production()
Check if, among the included subsystems, all are in production.
- Return type:
- Returns:
True if all the subsystems are in production, False otherwise.
-
csp_config:
CSPConfiguration|None= None
-
dishes_config:
DishesConfiguration|None= None
- get_included_subsystems()
Get the list of subsystems that are included in the configuration.
- Return type:
- Returns:
A list with the names of the included subsystems.
- get_subsystem_config(subsystem_name)
Get the configuration for a specific subsystem.
- Parameters:
subsystem_name (
SubsystemName) – The name of the subsystem you want to get the configuration for.- Return type:
- Returns:
The configuration for the specified subsystem.
- Raises:
ValueError – If the specified subsystem is not included in the configuration.
-
sdp_config:
SDPConfiguration|None= None
-
tmc_config:
TMCConfiguration|None= None
Config reader
Concrete and abstract tools to read the test harness configuration.
- class ska_integration_test_harness.config.reader.ConfigurationReader
A factory that reads from somewhere the test harness configuration.
This abstract class defines the interface of a configuration reader that is able to collect subsystem configurations and create a TestHarnessConfiguration object.
Currently, the supported subsystems are:
TMC
CSP
SDP
the dishes
the emulation configuration
None of those configurations are mandatory and the correctness of the configuration is not checked by this class, since it is delegated to a separate validator that can make its own checks according to the testing context.
The concrete implementations of this class may read the configuration from different sources, such as environment variables and configuration files.
- abstract get_csp_configuration()
Get the configuration for the CSP.
return: The CSP configuration (if any).
- Return type:
- abstract get_dish_configuration()
Get the configuration for the dishes.
return: The dishes configuration (if any).
- Return type:
- abstract get_mccs_configuration()
Get the configuration for the MCCS.
return: The MCCS configuration (if any).
- Return type:
MCCSConfiguration|None
- abstract get_sdp_configuration()
Get the configuration for the SDP.
return: The SDP configuration (if any).
- Return type:
- abstract get_target()
Get the target environment for the configuration.
return: The target environment for the configuration.
- Return type:
- get_test_harness_configuration()
Get all the configurations needed for the test harness.
- Return type:
- return: A collection of all the test harness configurations that
have been found by the reader.
- abstract get_tmc_configuration()
Get the configuration for the TMC.
return: The TMC configuration (if any).
- Return type:
- class ska_integration_test_harness.config.reader.YAMLConfigurationReader
A configuration reader that reads from a YAML file.
A configuration reader that creates the needed test harness configurations reading everything from a YAML file.
The YAML file must have the following structure:
tmc: is_emulated: false # Not supported otherwise, default is false # Expected device names (Required) centralnode_name: "ska_mid/tm_central/central_node" tmc_subarraynode1_name: "ska_mid/tm_subarray_node/1" tmc_csp_master_leaf_node_name: "ska_mid/tm_leaf_node/csp_master" tmc_csp_subarray_leaf_node_name: "ska_mid/tm_leaf_node/csp_subarray01" tmc_sdp_master_leaf_node_name: "ska_mid/tm_leaf_node/sdp_master" tmc_sdp_subarray_leaf_node_name: "ska_mid/tm_leaf_node/sdp_subarray01" tmc_dish_leaf_node1_name: "ska_mid/tm_leaf_node/d0001" tmc_dish_leaf_node2_name: "ska_mid/tm_leaf_node/d0036" tmc_dish_leaf_node3_name: "ska_mid/tm_leaf_node/d0063" tmc_dish_leaf_node4_name: "ska_mid/tm_leaf_node/d0100" csp: is_emulated: false # Supported true too, default is true # Expected device names csp_master_name: "mid-csp/control/0" csp_subarray1_name: "mid-csp/subarray/01" sdp: is_emulated: true # Supported false too, default is true # Expected device names (Required) sdp_master_name: "mid-sdp/control/0" sdp_subarray1_name: "mid-sdp/subarray/01" dishes: is_emulated: true # Supported false too, default is true # Expected device names (Required) dish_master1_name: "ska001/elt/master" dish_master2_name: "ska036/elt/master" dish_master3_name: "ska063/elt/master" dish_master4_name: "ska100/elt/master"
IMPORTANT NOTE: currently, the required fields and sections are hardcoded, but in the future, we may want to make them optional and elastic to permit the user to define the configuration only for the subsystems they need. This could be the starting point to make all the subsystem to be potentially optional and elastic.
- get_csp_configuration()
Get the configuration for the CSP.
return: The CSP configuration (if any).
- Return type:
- get_dish_configuration()
Get the configuration for the dishes.
return: The dishes configuration (if any).
- Return type:
- get_mccs_configuration()
Get the configuration for the MCCS.
return: The MCCS configuration (if any).
- Return type:
MCCSConfiguration|None
- get_sdp_configuration()
Get the configuration for the SDP.
return: The SDP configuration (if any).
- Return type:
- get_target()
Get the target environment for the subsystem (“mid” or “low”).
return: The “mid” or “low” target environment. Default is “mid”.
- Return type:
- get_tmc_configuration()
Get the configuration for the TMC.
return: The TMC configuration (if any).
- Return type:
- read_configuration_file(filename)
Read the YAML file and store the content as a dictionary.
Read the YAML file and store the content as a dictionary in the
config_as_dictattribute. Run this method before calling any other method that generates configurations.- Parameters:
filename (
str) – The path to the YAML file you want to read- Raises:
FileNotFoundError – If the file doesn’t exist.
yaml.YAMLError – If the file is not a valid YAML file.
- Return type:
Config validation
Concrete and abstract tools to validate the test harness configuration.
- class ska_integration_test_harness.config.validation.BasicConfigurationValidator(logger=None)
A basic configuration validator for the test harness.
This validator:
checks the presence of TMC, CSP, SDP and Dishes or MCCS configurations (depending on the target being Mid or Low)
ensure the required fields are set for each subsystem
ensure the device names are valid and that they point to reachable devices
ensure the consistency of the emulation settings
-
subsystem_validators:
SubsystemConfigurationValidator The validators used to validate the subsystem configurations.
- validate_subsystems_configurations(config)
Validate each individual subsystem configuration.
- Return type:
- class ska_integration_test_harness.config.validation.ConfigurationError(message)
A critical issue found in the configuration.
- class ska_integration_test_harness.config.validation.ConfigurationIssue(message)
A data structure to represent an issue found in a configuration.
A configuration issue is some anomaly in the configuration that may prevent the test harness from working correctly. It is generated by a validation procedure, it is defined by a message and it can be classified as critical (error) or non-critical (warning).
A critical issue will stop the test harness from starting, while a non-critical issue will only generate a warning.
All the issues will be logged (so this class instances - given a logger - they can log themselves).
- message
The message describing the anomaly.
- class ska_integration_test_harness.config.validation.ConfigurationValidator(logger=None)
A generic validator for the whole test harness configuration.
It provides interfaces for two main validation steps:
a step to validate the presence of a coherent set of subsystems (e.g., for TMC-Mid tests, we need TMC, CSP, SDP, and Dishes)
a step to validate the individual subsystems configurations (e.g., check the required fields, check the device are reachable, etc.)
It provides also a minimal log.info utility to log the validation steps.
- logger
An optional logger used to log the errors and warnings, while they are found during the validation.
- logger_prefix
The prefix used to log the validation messages.
- abstract validate_subsystems_configurations(config)
Validate the individual subsystem configurations.
Validate all subsystem configurations using the configured validators. If any critical error is found, a ValueError is raised.
If the logger is set, all the errors and warnings are logged.
- Raises:
ValueError – If any critical error is found.
- Return type:
- abstract validate_subsystems_presence(config)
Validate the presence of the required subsystems.
A given configuration must contain a valid subset of subsystems (i,e., there must be all the needed dependencies to run the tests).
If the logger is set, all the errors and warnings are logged.
- Raises:
ValueError – If any critical error is found.
- Return type:
- class ska_integration_test_harness.config.validation.ConfigurationWarning(message)
A non-critical issue found in the configuration.
- class ska_integration_test_harness.config.validation.SubsystemConfigurationValidator(logger=None)
A generic validator for a subsystem configuration.
This is an abstraction of a class that can be used to validate one or more subsystem configurations. The idea is that you can implement your own validation logic by subclassing this class and implementing the
validatemethod. The validation logic should scan a given configuration and populate theerrors_and_warningslist with the errors and warnings found during the validation (using theadd_errorandadd_warninghelper methods).With a validator instance, you can run multiple validations on different configurations, and collect all the errors and warnings in the
errors_and_warningslist, or you can reset the validator and start a new validation. After one or more runs, you can check if the validation was successful by calling theis_validmethod.When you initialise an instance of this class, you can pass an optional logger that will be used to log the errors and warnings found during the validation.
NOTE: maybe in future this could be refactored with the https://refactoring.guru/design-patterns/visitor design pattern (and so implement custom checks for each different kind of subsystem configuration).
- add_error(message)
Add an error to the errors and warnings list.
- add_warning(message)
Add a warning to the errors and warnings list.
-
errors_and_warnings:
list[ConfigurationIssue] The errors and warnings found during one or more validations.
- get_critical_errors()
Return the critical errors found during the validation.
- Return type:
- Returns:
A list of critical errors.
- logger
An optional logger used to log the errors and warnings, while they are found during the validation.
- abstract validate(config)
Validate a generic subsystem configuration.
Validate a generic subsystem configuration, implementing your own checks to ensure that the configuration is correct. As a side effect, this method should populate the
errors_and_warningslist with the errors and warnings found during the validation.NOTE: this method is not expected to return anything, nor to raise exceptions.
- Parameters:
config (
SubsystemConfiguration) – The configuration to validate.
Init(ialisation) module
Tools to initialise the test harness.
(see Installation and Usage for more information on how to use them)
- class ska_integration_test_harness.init.TestHarnessBuilder
A builder class for configuring and building a test harness.
This class allows you to configure various subsystems such as TMC, CSP, SDP, and Dishes, validate the configurations, and build the test harness. You can/should also specify some default JSON inputs for various commands/purposes.
The class uses also a set of tools to read and validate the configurations and the inputs. Potentially, you can inject your own tools to customise the behaviour of the builder. Potentially, you can also subclass this builder to customise its behaviour.
Usage example:
builder = TestHarnessBuilder() builder.read_config_file("configurations.yaml") builder.validate_configurations() default_inputs = TestHarnessInputs( assign_input=FileJSONInput("path/to/assign.json"), # ... ) builder.set_default_inputs(default_inputs) builder.validate_default_inputs() telescope = builder.build()
- build()
Build the test harness.
- Return type:
- Returns:
a wrapper for the telescope and its subsystems.
-
config:
TestHarnessConfiguration The configuration used to build the test harness.
-
config_reader:
YAMLConfigurationReader The tool used to read the configurations.
-
config_validator:
ConfigurationValidator The tool used to validate the configurations.
-
default_inputs:
TestHarnessInputs The default inputs used to build the test harness.
-
input_validator:
InputValidator The tool used to validate the inputs.
-
kube_namespace:
str|None The Kubernetes namespace where the SUT is deployed.
It is optional, but if you set it, it will be used to connect to get more information about the various subsystem devices.
- read_config_file(filepath)
- Read the configuration from a YAML file and set the configurations
accordingly.
- Parameters:
filepath (
str) – The path to the YAML file containing the configurations.- Return type:
- Returns:
The current instance of TestHarnessBuilder with configurations set.
- Raises:
FileNotFoundError – If the YAML file does not exist.
yaml.YAMLError – If the YAML file contains errors.
- set_default_inputs(inputs)
Set the default inputs for the test harness.
NOTE: all the defaults input specified by the
TestHarnessInputsclass are required, since they are used in test harness tear down procedures.- Parameters:
inputs (
TestHarnessInputs) – The default inputs to be used.- Return type:
- Returns:
The current instance of TestHarnessBuilder with the default inputs set.
- set_kubernetes_namespace(kube_namespace)
Set the Kubernetes namespace where the SUT is deployed.
It will help to connect to the ska-k8s-config-exporter service.
- Parameters:
namespace – The Kubernetes namespace.
- Return type:
- Returns:
The current instance of TestHarnessBuilder with the Kubernetes namespace set.
-
test_harness_factory:
TestHarnessFactory The factory used to create the subsystems and the overall telescope wrapper.
- validate_configurations()
Validate all subsystem configurations.
Logs errors and warnings as necessary.
If any configuration is invalid, a ValueError is raised, containing a detailed list of all validation errors and warnings.
- Return type:
- Returns:
The current instance of TestHarnessBuilder if all configurations are valid.
- Raises:
ValueError – If any configuration is invalid, with the details of the errors.
- validate_default_inputs()
Check if all default inputs are valid.
Right now this method checks: :rtype:
TestHarnessBuilderall the required default inputs are passed
all the required default inputs have the right type
- Raises:
ValueError – if some input is missing.
- class ska_integration_test_harness.init.TestHarnessFactory(config, default_inputs)
Factory to build the test harness wrappers.
Given the necessary inputs and configurations, build the various test harness subsystems (dealing with emulation directives, variants coming from configuration settings, etc.).
Main methods:
create_tmc_wrapper: Create a wrapper for the TMC subsystem.
create_csp_wrapper: Create a wrapper for the CSP subsystem.
create_sdp_wrapper: Create a wrapper for the SDP subsystem.
create_dishes_wrapper: Create a wrapper for the Dishes subsystem.
- create_csp_wrapper()
Create a CSP wrapper with the given configuration.
- Return type:
- Returns:
The CSP wrapper.
- create_dishes_wrapper()
Create a dishes wrapper with the given configuration.
- Return type:
- Returns:
The dishes wrapper.
- create_mccs_wrapper()
Create a MCCS wrapper with the given configuration.
- Return type:
MCCSWrapper- Returns:
The MCCS wrapper.
- create_sdp_wrapper()
Create a SDP wrapper with the given configuration.
- Return type:
- Returns:
The SDP wrapper.
- create_telescope_wrapper()
Create a telescope wrapper with the given configuration.
- Return type:
- Returns:
The telescope wrapper.
- create_tmc_wrapper()
Create a TMC wrapper with the given configuration.
- Return type:
- Returns:
The TMC wrapper.
- set_config(config)
Set the configuration for the factory.
- Parameters:
config (
TestHarnessConfiguration) – The new configuration.
- set_default_inputs(default_inputs)
Set the default inputs for the factory.
- Parameters:
default_inputs (
TestHarnessInputs) – The new default inputs.
Common utils module
A collection of common utilities for the test harness.
At the moment, this module contains the following utilities:
DevicesInfoProvider: A class that can be used to get information about Tango devices from the k8s-config-info service (and various support classes).
- class ska_integration_test_harness.common_utils.DevicesInfoProvider(kube_namespace, service_name='ska-k8s-config-exporter-service', port=8080, path='tango_devices')
Provider to get info about Tango devices from ska-k8s-config-exporter.
This class communicates with the ska-k8s-config-exporter service to fetch and store the information about Tango devices. It also provides methods to retrieve device information and recaps.
- DEFAULT_PATH = 'tango_devices'
Default path to interrogate the ska-k8s-config-exporter service.
- DEFAULT_PORT = 8080
Default port where the ska-k8s-config-exporter service is listening.
- DEFAULT_SERVICE_NAME = 'ska-k8s-config-exporter-service'
Default name of the ska-k8s-config-exporter service.
- get_device_recap(device_name)
Get a recap of the given device information.
The recap contains the device name and its information (for now, only the version). If the device is not found, it is reported in the recap.
- get_update_service_url()
Get the URL to update the devices information.
- Return type:
- Returns:
URL to update the devices information.
- kube_namespace
Kubernetes namespace where the service is running.
-
last_devices_info:
dict[str,TangoDeviceInfo] Last devices information fetched from the service.
- path
Path to interrogate on the service to get all devices.
- port
Port where the service is listening.
- service_name
Name of the ska-k8s-config-exporter service.
- update(timeout=10)
Update the local devices information by calling the service.
Fetches the latest devices information from the ska-k8s-config-exporter service and updates the internal state.
- Parameters:
timeout (
int) – Timeout in seconds for the request (default: 10 seconds).- Raises:
DevicesInfoServiceException – If the service is not available, or it does not respond as expected.
- Return type:
- exception ska_integration_test_harness.common_utils.DevicesInfoServiceException(message='')
The devices information is not available or something went wrong.
i.e., when the ska-k8s-config-exporter service is not available.
- class ska_integration_test_harness.common_utils.TangoDeviceInfo(name, version=None)
Information about a Tango device from ska-k8s-config-exporter.
Information related to a Tango device collected by the ska-k8s-config-exporter tool. The information includes:
the name of the device (always present)
the version of the device (may be None)
- get_recap(include_version=True)
Get a recap of the device information.
Get a recap of the device information. The recap contains the device name and its information (for now, only the version if available).