-
Notifications
You must be signed in to change notification settings - Fork 1
I need another command for my application
This is going to require changes in the FSM configuration that you are using, and potentially in drunc's FSMaction.
There are several things to consider:
- Are you absolutely sure you can't fit the behaviour you need into the existing DAQ FSM?
- When should user be allowed to run the command?
- Do you need to pass parameter(s) to the application at run time?
What is the goal? I want drunc
to execute the following code in my module:
void MyFancyModule::do_reconfigure(const data_t& /*payload*/) // Note that we do not use payload here
{
TLOG() << "Reconfiguring";
reconfigure(); // whatever that may do
TLOG() << "DONE Reconfiguring";
}
The first thing to do is to register this command in the constructor of the module:
MyFancyModule::MyFancyModule(const std::string& name):
dunedaq::appfwk::DAQModule(name)
{
register_command("reconfigure", &MyFancyModule::do_reconfigure);
// ...
}
If you are not interested in the payload from the run control, you just need to change the FSM configuration. An example of FSMconfiguration
can be found around here.
To create a new command, you need to first decide when the user should be able to use it. We are going to create a command that takes us from one state and lands us in the same state (loopback transition).
You can see all the transitions listed in the FSMconfiguration
object, for example:
<obj class="FSMconfiguration" id="fsmConf-test">
<!-- ... -->
<rel name="transitions">
<ref class="FSMtransition" id="conf"/>
<ref class="FSMtransition" id="start"/>
<ref class="FSMtransition" id="enable_triggers"/>
<ref class="FSMtransition" id="disable_triggers"/>
<ref class="FSMtransition" id="drain_dataflow"/>
<ref class="FSMtransition" id="stop_trigger_sources"/>
<ref class="FSMtransition" id="stop"/>
<ref class="FSMtransition" id="scrap"/>
</rel>
</obj>
A bit further, you can see the definition of the transitions themselves, for example:
<obj class="FSMtransition" id="scrap">
<attr name="source" type="string" val="configured"/>
<attr name="dest" type="string" val="initial"/>
</obj>
This tells the run control what happens when you issue scrap
. It moves you from source: configured
to destination (dest): initial
state.
Let's assume that we want our reconfigure
command to runs from the configured
state:
<obj class="FSMtransition" id="reconfigure">
<attr name="source" type="string" val="configured"/>
<attr name="dest" type="string" val="configured"/>
</obj>
You see here, the reconfigure
command takes you from the configured
state and lands you back in the configured
state.
Of course, you will need to add it in the FSMconfiguration
transitions relations:
<obj class="FSMconfiguration" id="fsmConf-test">
...
<rel name="transitions">
<ref class="FSMtransition" id="conf"/>
<ref class="FSMtransition" id="reconfigure"/> <!-- New -->
<!-- ... -->
</rel>
</obj>
If you attach that FSMconfiguration
to your controllers (and note that each controller could have a different FSM, so you will need to change it for each FSMconfiguration
used), when you start drunc
, you should have access to reconfigure
after conf
.
Well, source
supports regex, so you can always define your transition as:
<obj class="FSMtransition" id="reconfigure">
<attr name="source" type="string" val="configured|ready|running|dataflow_drained"/>
<attr name="dest" type="string" val=""/>
</obj>
... and now you can execute reconfigure
from the configured
, ready
, running
, dataflow_drained
.
Note that if you do that, you must add an empty value in dest
.
What is the goal? I want drunc
to execute the following code in my module:
void MyFancyModule::do_reconfigure(const data_t& payload) // Note that the payload is important
{
TLOG() << "Reconfiguring";
reconfigure(payload["new_oks_file"]); // whatever that may do
TLOG() << "DONE Reconfiguring";
}
First, follow all the instructions above.
Then, you need to define an FSMaction
that defines the parameters you want from the user in the run control shell to the parameter in the application. This FSMaction
should define pre_transition
methods than do the conversion from userland to applicationland.
Right now, all the FSMaction
are in drunc
(that may change in the future). They are defined here.
Let's continue with our reconfigure
example. Suppose one of the parameter I want to pass down to the user is a choice of the filename that is used to configure the daq_application (I know, we'd never do that, but whatever). Suppose that these files are somehow "keyed" such that the user doesn't need to actually type the full path, but rather an identifier:
-
config1
maps to/some/oks/file/somewhere.data.xml
-
config2
maps to/another/oks/file/somewhere.data.xml
So now, I want the shifter to be able to type:
# ...
drunc-unified-shell > reconfigure --configuration-identifier config1
# ...
drunc-unified-shell > reconfigure --configuration-identifier config2
To do that, you can create a file called reconfigurer.py
in drunc/src/fsm/actions
with the following code:
from drunc.fsm.core import FSMAction
from drunc.exceptions import DruncException
def UnknownReconfigurationKey(DruncException):
pass
class Reconfigurer(FSMAction):
def __init__(self, configuration):
super().__init__(
name = "reconfigurer"
)
self.configuration_map = self.parse(configuration)
def pre_reconfigure(self, _input_data, _context, configuration_identifier:str, **kwargs):
file_name = None
if configuration_identifier not in self.configuration_map:
raise UnknownReconfigurationKey(configuration_identifier)
_input_data['new_oks_file'] = self.configuration_map[configuration_identifier]
return _input_data
def parse(self, oks_conf):
pass # AKA pain and tears
What's happening here?
- We create an
UnknownReconfigurationKey
exception fromDruncException
(when you are throwing exception that you know about, please always inherit them fromDruncException
or its derivative). - We create an
FSMaction
calledReconfigurer
:- The
__init__
method names theFSMaction
, and uses theFSMaction
OKS configuration to configure itself. Theparse
method is left to the imagination of the reader. The configuration will be discussed a bit more later on. - The
pre_reconfigure
method gets executed right before we sendreconfigure
to the children (application or controller). Some important points:- The name of the function must be f
pre_{command_name}
. - The parameters of this function are very very (very) important:
-
_input_data
is a dictionary that get shipped to the DAQ module as is, so this "new_oks_file" entry is what the C++ will need to use to access this parameter (see the C++ example a bit higher). -
_context
is the controller object (it's sometimes useful to know who sent the command etc. as seen in this example) - There should always be a
**kwargs
argument, that you are not allowed to use in this method (basically, they are the arguments of other actions). - The rest of the parameters are fed back to the user in the shell:
- They must have a type annotation (and they can only be
bool
,int
,string
, orfloat
) - They can have a default, in which case the argument in the shell will be non-mandatory. If they don't, they will be mandatory (like in this case, the user will not be able to issue
reconfigure
, they will always have to specifyreconfigure --configuration-identifier
). - Bad things can happen if 2 FSMaction use the same parameter name for the same transition (I've never tested it, but I pretty much guarantee it)
- They must have a type annotation (and they can only be
-
- This function must always return a dictionary
_input_data
, even if it doesn't do anything to it. - Anything in between that is more or less free (create a file, connect to ELisA, issue calls to a microservice, or more prosaically, ask the run number and run type to the user).
- The name of the function must be f
- The
Finally, the action needs to be registered in drunc
by adding it in the FSMActionFactory
here.
OK, that concludes the changes to drunc
.
We are not done though. If we want our Reconfigurer
action to be used, we have to go and modify OKS once more.
Note: I know this isn't nice, we are working on getting this better and redesigning the FSM schema.
First thing is the configuration of the FSMAction itself, for this you need to add:
<obj class="FSMaction" id="reconfigurer">
<attr name="name" type="string" val="reconfigurer"/> <!-- Isn't this the same as above? Yes it is -->
<rel name="parameters">
<ref class="Variable" id="config1-key"/>
<ref class="Variable" id="config2-key"/>
</rel>
</obj>
<obj class="Variable" id="config1-key">
<attr name="name" type="string" val="config1-key"/>
<attr name="value" type="string" val="/some/oks/file/somewhere.data.xml"/>
</obj>
<!-- ... -->
As you see, the FSMaction
can only have key-value configuration, which are all in the parameters
relationship. Yes, this isn't great, but believe me, you do not want to go and define a schema for every FSMaction
(at least, not right now).
Now, let's hook our action into the FSMconfiguration
. First, we need to tell drunc
we want to use this action, so let's add it in the actions
relationships:
<obj class="FSMconfiguration" id="fsmConf-test">
<!-- ... -->
<rel name="actions">
<!-- ... -->
<ref class="FSMaction" id="reconfigurer"/>
</rel>
</obj>
Then, we need to say when and how the pre_reconfigure
action gets executed. In this case, this is a bit useless, however, there are cases where the execution order of these actions matter. Similarly, if an exception get thrown by the action, whether you want to abort the execution of the transition needs to be configured. All of the above information is encoded in the FSMxTransition
configuration object (where x
stands either for pre
or post
).
Let's have a look at an example for a pre start transition:
<obj class="FSMxTransition" id="pre_start_test">
<attr name="transition" type="string" val="start"/>
<attr name="order" type="string">
<data val="user-provided-run-number"/>
<data val="file-run-registry"/>
</attr>
<attr name="mandatory" type="string">
<data val="user-provided-run-number"/>
<data val="file-run-registry"/>
</attr>
</obj>
You can see that the order at which the actions are executed is in the order
attribute. Similarly, if an exception get thrown in any of the mandatory
actions, the transition is interrupted (in this case, if the user didn't provide a viable run type, run number or trigger rate, and if the configuration cannot be saved in PWD).
So, our reconfigure
pre_transition could look like:
<obj class="FSMxTransition" id="pre_reconfigure">
<attr name="transition" type="string" val="reconfigure"/>
<attr name="order" type="string">
<data val="reconfigurer"/>
</attr>
<attr name="mandatory" type="string">
<data val="reconfigurer"/>
</attr>
</obj>
We can then add our pre_reconfigure
in our FSMConfiguration
:
<obj class="FSMconfiguration" id="fsmConf-test">
<!-- ... -->
<rel name="pre_transitions">
<ref class="FSMxTransition" id="pre_reconfigure"/>
</rel>
<!-- ... -->
</obj>
And that's it.
Couple of points:
- You generally only want to execute
FSMAction
once, this means that theroot-controller
is likely to have all thepre_transitions
/post_transitions
/actions
and none of the other controllers' FSM configuration. Remember though, you still need to add thereconfigure
transition in all theFSMConfiguration
of all the controllers. -
pre_transition
s run before the transitionpost_transition
s run after the transition. You cannot inject data in post_transition to get to the application, because the transition has already happen, however it is practical to use post transitions for other things (such as thread pinning).
- Home
- Release notes
- Roadmap
- Check before merging
- Setup
- Operation
- Developers
- Testing