Important

This page is deprecated. Please use our new platform and accompanying documentation.

Configuring a MAGI Agent at Runtime

In Writing MAGI Agents, you saw how to create a basic agent. The sample agent created a single file on a test node. This document will explain how to use configuration in the AAL file to configure an agent at runtime.

Setting Agent Configuration

This document will expand the sample code of our FileCreator example. For reference, here is the agent code:

  from magi.util.agent import DispatchAgent, agentmethod
  from magi.util.processAgent import initializeProcessAgent

  # The FileCreator agent implementation, derived from DispatchAgent.
  class FileCreator(DispatchAgent):
      def __init__(self):
          DispatchAgent.__init__(self)
          self.filename = '/tmp/newfile'

      # A single method which creates the file named by self.filename.
      # (The @agentmethod() decorator is not required, but is encouraged.
      #  it does nothing of substance now, but may in the future.)
      @agentmethod()
      def createFile(self, msg):
          **Create a file on the host.**
          # open and immediately close the file to create it.
          open(self.filename, 'w').close()

  # the getAgent() method must be defined somewhere for all agents.
  # The Magi daemon invokes this mehod to get a reference to an
  # agent. It uses this reference to run and interact with an agent
  # instance.
  def getAgent():
      agent = FileCreator()
      return agent

  # In case the agent is run as a separate process, we need to
  # create an instance of the agent, initialize the required
  # parameters based on the received arguments, and then call the
  # run method defined in DispatchAgent.
  if __name__ "__main__":
      agent = FileCreator()
      initializeProcessAgent(agent, sys.argv)
      agent.run()

If we reset the self.filename variable in the agent before invoking createFile in the AAL, we can change the file that is created.

The base class DispatchAgent itself is derived from a class that will let us do this. The Agent class implements two methods:

  • setConfiguration - Sets the passed parameters as class instance variables.
  • confirmConfiguration - This method is meant to be re-implemented in your agent if you need confirm the variables set are valid for your agent.

To set the self.filename variable in the FileCreator Agents, we modify the AAL to include a call to the Agent method setConfiguration, passing in a list of key-value pairs. (In the following example, it is a single key-value pair.)

- type: event
  agent: myFileCreators
  method: setConfiguration
  args:
     filename: /tmp/myCreatedFile

Note that you do not specify self when referencing an Agent variable. We make sure to place this event in the AAL event stream prior to the createFile event. The complete AAL file is:

  streamstarts: [main]

  groups:
      myFileCreatorGroup: [NODES]

  agents:
      myFileCreators:
          group: myFileCreatorGroup
          # (note: the "PATH" argument is the agent directory. The
          # directory must contain an IDL and agent implementation. It must
          # also contain a *__init__.py* file, which is required
          # for it to be considered as a valid python package.)
          path: PATH/FileCreator
          execargs: []

  eventstreams:
      main:
          - type: event
            agent: myFileCreators
            method: setConfiguration
            args:
               filename: /tmp/myCreatedFile

          - type: event
            agent: myFileCreators
            method: createFile
            args: {}

Now when we run the Agent again (possibly using agentTool to restart the Magi daemons), we see the following events:

  $ magi_orchestrator.py --project $project --experiment $experiment --events ./myEvents.aal -o run.log -v
  stream initialization : sent : joinGroup myFileCreatorGroup --> __ALL__
  stream initialization : done : trigger GroupBuildDone myFileCreatorGroup  complete.
  stream initialization : sent : loadAgent myFileCreators --> myFileCreatorGroup
  stream initialization : done : trigger AgentLoadDone  myFileCreators complete.
  stream initialization : DONE : complete.
  stream main           : sent : setConfiguration(['/tmp/myCreatedFile ... ) --> myFileCreatorGroup
  stream main           : sent : createFile(None) --> myFileCreatorGroup
  stream main           : DONE : complete.
  $ ssh myNode.myExperiment.myGroup ls -l /tmp/myCreatedFile
  -rw-r--r-- 1 root root 0 Mar  5 13:55 /tmp/myCreatedFile
  $

And we see that our specified file, /tmp/myCreatedFile was created.

Confirming Valid Configuration

This works well, but the input to the Agent is free-form. What if the user gives invalid input, like the wrong type or data that is not in a valid range? This is where the Agent confirmConfiguration method comes into play.

confirmConfiguration should be written for any Agent that wants to validate its state. It gets invoked in the AAL file after the user invokes setConfiguration.

Note: The concept of an Agent confirming user input will change in future releases of MAGI. The Orchestrator (or other MAGI/Montage components) will use the interface specification in the Agent’s IDL file to ensure the input to the agent is valid.

Suppose our sample agent wanted to allow the user to create a file in only the /local directory on the host machine. The confirmConfiguration method that does this is:

  def confirmConfiguration(self):
      **Make sure the user input is a string value and starts with
      "/local".**
      if not isinstance(self.filename, (str, unicode)):
          return False

      if not self.filename.startswith('/local'):
          return False

      return True

When we add this method to our sample Agent, and run the experiment with the existing AAL file, which contains configuration that does not start with /local, the Orchestrator gives us an error while executing the event stream:

  $ magi_orchestrator.py --project $project --experiment $experiment --events ./myEvents.aal -o run.log -v
  stream initialization : sent : joinGroup myFileCreatorGroup --> __ALL__
  stream initialization : done : trigger GroupBuildDone myFileCreatorGroup  complete.
  stream initialization : sent : loadAgent myFileCreators --> myFileCreatorGroup
  stream initialization : done : trigger AgentLoadDone  myFileCreators complete.
  stream initialization : DONE : complete.
  stream main           : sent : setConfiguration(['/tmp/myCreatedFile ... ) --> myFileCreatorGroup
  stream unknown        : exit : method setConfiguration returned False on agent unknown in group unknown and on node(s): moat.
  $

The Orchestrator exited with an error, as it should.

If we now modify the AAL file to include a valid configuration, the Orchestrator succeeds. The updated AAL fragment is:

  - type: event
    agent: myFileCreators
    method: setConfiguration
    args:
       filename: /local/myGreatFile

When we run the Orchestrator with the modified AAL, it succeeds as the agent configuration is now valid:

  $ magi_orchestrator.py --project $project --experiment $experiment --events ./myEvents.aal -o run.log -v
  stream initialization : sent : joinGroup myFileCreatorGroup --> __ALL__
  stream initialization : done : trigger GroupBuildDone myFileCreatorGroup  complete.
  stream initialization : sent : loadAgent myFileCreators --> myFileCreatorGroup
  stream initialization : done : trigger AgentLoadDone  myFileCreators complete.
  stream initialization : DONE : complete.
  stream main           : sent : setConfiguration(['/local/myGreatFile ... ) --> myFileCreatorGroup
  stream main           : sent : createFile(None) --> myFileCreatorGroup
  stream main           : DONE : complete.
  $

And the “valid” file has been created on the machine:

  $ ssh myNode.myExperiment.myGroup ls -l /local
  total 4
  drwxrwxr-x 2 glawler Deter 4096 Mar  5 08:35 logs
  -rw-r--r-- 1 root    root     0 Mar  5 14:33 myGreatFile
  $

Triggers and Event Stream Sequence Points

If you run the AAL and Agent code above, you may see that it does not actually work. One small needed detail has been left out of the AAL file. Normally the Orchestrator will run through the events in the AAL as fast is it can. If we used the event streams in the AAL file as it now stands:

  eventstreams:
      main:
          - type: event
            agent: myFileCreators
            method: setConfiguration
            args:
                filename: /local/myGreatFile

          - type: event
            agent: myFileCreators
            method: createFile
            args: {}

The Orchestrator will send two messages to the Agents in rapid succession: the setConfiguration and createFile event messages. If the setConfiguration call returns False, which it will given invalid input, the Orchestrator will not receive the message because would have sent the messages and exited.

Therefore, we need a way to tell the Orchestrator to wait for a response from setConfiguration before continuing. This is done by inserting a small pause, using a trigger which times out after 3 seconds:

  # Wait 3 seconds for a response to setConfiguration
  # timeout value is in milliseconds.
  - type: trigger
    triggers: [{timeout: 3000}]

If we insert this trigger between setConfiguration and createFile, the Orchestrator will receive the error message from the agent and exit on error.

The full AAL file now is:

  streamstarts: [main]

  groups:
      myFileCreatorGroup: [witch, moat]

  agents:
      myFileCreators:
          group: myFileCreatorGroup
          path: PATH/FileCreator
          execargs: []

  eventstreams:
      main:
          - type: event
            agent: myFileCreators
            method: setConfiguration
            args:
                filename: /local/myGreatFile

          - type: trigger
            triggers: [{timeout: 3000}]

          - type: event
            agent: myFileCreators
            method: createFile
            args: {}

But how do we know that waiting for 3 seconds is a long enough time to wait? Wouldn’t it be better if we could tell the Orchestrator to wait for a response from the agent before continuing?

We can do this using a named trigger. We add a trigger statement to the setConfiguration event clause and modify the trigger to wait for that event before continuing to process the event stream:

- type: event
  agent: myFileCreators
  trigger: configDone
  method: setConfiguration
  args:
      filename: /local/myGreatFile

# Wait for the event "configDone" from all fileCreator agents.
- type: trigger
  triggers: [{event: configDone, agent: myFileCreators}]

Now when setConfiguration is called on the Agent, the daemon will send a trigger with the event configDone after the method has returned. With this modified trigger, the Orchestrator will wait for the trigger event configDone before processing the next event in the event stream.

Here is the Orchestrator output now. Note that setConfiguration now “fires” a trigger (sends a trigger) and the Orchestrator waits until the trigger is resolved before moving on.

  $ magi_orchestrator.py --project $project --experiment $experiment --events ./myEvents.aal -o run.log -v
  stream initialization : sent : joinGroup myFileCreatorGroup --> __ALL__
  stream initialization : done : trigger GroupBuildDone myFileCreatorGroup  complete.
  stream initialization : sent : loadAgent myFileCreators --> myFileCreatorGroup
  stream initialization : done : trigger AgentLoadDone  myFileCreators complete.
  stream initialization : DONE : complete.
  stream main           : sent : setConfiguration(['/local/myGreatFile ... ) --> myFileCreatorGroup  (fires trigger: configDone)
  stream main           : done : trigger configDone  complete.
  stream main           : sent : createFile(None) --> myFileCreatorGroup
  stream main           : DONE : complete.
  $

For reference, the new agent implementation, AAL file, and IDL, file can be downloaded as a tar file here: FileCreator-withconfig.tbz.