The ABLE
Agent Platform
ABLE 2.0
June 30, 2003
The following topics are discussed in this document:
· Introduction, page 3
· Platform Services, page 4
· Platform Agents, page 5
· Platform Security, page 5
· Creating a Platform Agent, page 6
· Configuring the Platform, page 7
· Configuration Basics, page 8
· Platform Name, page 8
· Cryptography Parameters, page 8
· Principals, page 8
· Trust Levels, page 9
· Agent Pools, page 9
· Services, page 10
· Permitted Agents, page 11
· Setting Java Permissions, page 13
· Starting the ABLE Platform, page 13
· Running Platform Agents, page 14
The com.ibm.able.platform
package makes it easy to set up an environment wherein Agents and
Agent Services can be created and distributed across many disparate computer
systems. Of course, the systems involved must be able to run Java and be connected
to a network. This loose collection of computer systems, with each system
running one or more Java Virtual Machines (JVMs) each at a different port, and each
JVM containing zero or more agent services and zero or more agents, comprise an
agent platform. Figure 1 shows two overlapping Able platforms, Platform
A and Platform B:
Figure 1.
In the figure, Platform A consists of Systems A, B, C, D, E, and F. Platform B consists of Systems A, B, C, G, H, and J. Systems A, B, and C belong to both platforms. As shown, each system can have one or more JVMs and each JVM is addressable by its system name and unique port number through a Java RMI Registry. Not shown in the figure are agents and agent services running within the JVMs.
Within a platform, agents can freely communicate with other agents in two ways: by direct remote method invocation, and by sending and receiving JAS Transport Messages. Agents can also interact with agent services, and this is always done by direct method invocation.
· Verifiable Agent Naming Service
The verifiable agent naming service acts as a certificate authority, handing out a globally unique, verifiable Agent Name to each agent. Agents can use the naming service to verify that agent names sent to them have not been tampered with.
· Verifiable Agent Directory Service
An agent can use the directory service to obtain an empty Agent Description, fill out the description with pertinent information about itself, and then register the description with the directory service. All agents can search the directory to find other agents with which they might want to communicate. An agent can update its own description at any time.
· Agent Lifecycle Service
The agent lifecycle service can be used to create and initialize instances of agents anywhere within the platform. Agents can be suspended, resumed, and terminated, but only by other agents with the proper authority. Agent Lifecycle can create only those agents that have been predefined to the platform, so that rogue agents cannot be easily introduced into the platform.
· Platform Persistence Service
The persistence service allows other services and agents to persist information to an external database so that vital information can be recovered in the event of a crash and restart.
All of the above services implement an Able Platform Service Event Generator interface, so that other Java entities can register and deregister as service event listeners with the services. Listeners must implement the AblePlatformServiceEventListener interface.
· Message Transport System
The message transport system lets agents send Java Agent Services (JAS) Transport Messages to, and receive Transport Messages from, other agents.
In addition to all the above
services, the platform package provides a base class, an AblePlatformDefaultAgent
, from which
other platform agents can be derived. The AblePlatformDefaultAgent
contains no special logic to perform any unique task (that must be added in
derived agents), but it does make it easy to perform the following functions:
· obtain access to all the platform services
· obtain a unique, verifiable agent name from the agent naming service
· register a description with the Agent Directory Service so that other agents can find it
All this can happen automatically when the ABLE platform agent is initialized.
The AblePlatformDefaultAgent
also provides methods to make it easy to send JAS Transport Messages to and
receive JAS Transport Messages from other platform agents. At least one JAS
Locator is placed into each agent’s description, and this Locator can be used
by other agents to route messages to the Locator’s owning agent. An AblePlatformDefaultAgent
, if it
configures itself so, can have many Locators in its agent description, allowing
it to receive Transport Messages at many different access points.
Note that an AblePlatformDefaultAgent
can also place another type of Locator into its agent description. This
special, Able-defined Locator contains an RMI reference to the agent so that
other agents can directly invoke remote methods on the agent.
When security is on, the Verifiable Naming Service takes on
the role of a certificate authority, and Able uses Verifiable Agent Names (or
VANs), AbleSecureKeys
,
defined Kerberos principals, defined trust levels, and other mechanisms to
authenticate and authorize one agent to another.
To control the security setting for the platform, use a text
editor to edit the file able.preferences
and change the Security=
entry.
Note that when security is on, Able requires access to the Java Generic Security Services (JGSS) API, which must be installed in the Java environment. JGSS in turn requires Kerberos. JGSS is not shipped with Able.
To make it easy to add security checking to Java entities
that want to interact with the Able platform, Able has created an AbleSecuritySupport
object that can
be plugged into any Java object. AbleSecuritySupport
knows how to create and decode AbleSecureKey
s
used on secure method calls, and use
the naming service to verify agent names. When security is on, an AblePlatformDefaultAgent
will
automatically acquire an AbleSecuritySupport
object for itself.
Because AblePlatformDefaultAgent
is derived from AbleDefaultAgent
,
many of the old methods can be used just as before. However, many of the
methods will no longer work in a secure environment. Instead, new methods, that
take an AbleSecureKey
,
must be used in their place.
Creating a platform agent is simple; as already mentioned,
just derive the new agent from AblePlatformDefaultAgent
.
Everything the agent needs to operate in the platform is already provided in
this base class. For example, when the agent is constructed, it will
automatically obtain addressability to the available agent services and then
obtain a globally unique verifiable Agent Name from the Agent Naming Service.
When the agent is initialized, it will create an Agent Description for itself
and then, by default, register the description with the Agent Directory Service,
but the agent can be customized so that it doesn’t automatically register with
this service.
In the usual Able paradigm, an agent is first constructed, then it is customized by calling the agent’s methods, and then the agent is initialized so that it begins running. In the platform world, agents are typically self-customizing (from within their constructor methods) and may also be self-initializing (also from within the constructor methods). This is because it is not wise to let “outside,” untrusted code configure an agent. Note that the Agent Lifecycle Service, when used to create an instance of an agent, can pass predefined customization parameters to an agent’s constructor method, and that it can also pass predefined initialization parameters to an agent’s initialization method.
Of course, it is possible to define new secure methods for
an agent that may only be used by other Java objects with the proper authority.
Because all ABLE platform agents are also RMI remote objects, you can add
secure remote methods to your agent by first defining an interface that your
agent can implement; then implement those methods in your agent; and finally
compile the agent with rmic. To be secure, these methods should take an AbleSecureKey
object as a parameter.
You can customize your agent so that it will register with an RMI registry, but by default this will not happen since the default agent is setup to register with the Agent Directory Service instead.
By default, an AblePlatformDefaultAgent receives JAS
Transport Messages at its receiveMessage(TransportMessage)
method. This method, in turn, simply calls the old standby process(Object)
method, which does
nothing but simply log the message. Either of these methods, as desired, can
be overridden to do any special processing that is required.
Review the API documentation of AblePlatformDefaultAgent
for further
details on any of these and other agent behaviors.
· What computer systems you want to use in the agent platform
· How many Java Virtual Machines (hereafter called Agent Pools) are available on each system
· Which agent services run in which Agent Pools
· Which agents are allowed on the platform
· Which agents are permitted to run in which Agent Pools
Furthermore, if security is on, you will need to define
· Which Kerberos principals are allowed in the platform
· Trust levels for each principal and Agent Pool
To begin, you need to find the ableplatform.preferences
file in
your ABLE installation’s examples
directory and copy it to the directory
specified by your ABLE_HOME
environment variable, or, if that variable is not set, to your ABLE installation’s
bin
directory. Then,
using any text editor, edit the file using the comments in the file and the
instructions here to guide you.
In the preference file, lines that begin with the ‘#’ character are comments. You may freely add comments to the file. Except for blank lines, all other lines are configuration parameters.
Some parameters are specified in numbered groups. When adding, changing, or deleting groups, you must make sure that the numbers begin with 1 and are consecutive. ABLE configuration code will not “see” any groups after a gap in the numbering sequence. For example, if you are configuring AgentPools and you have AgentPool.1, AgentPool.2, AgentPool.3, and AgentPool.5, ABLE will not know about AgentPool.5 since AgentPool.4 isn’t specified.
Some of the configuration parameters might be for a future enhancement, and even though they are documented here, they may have no effect on the operation of the platform.
The following tables describe the configuration parameters.
PlatformName |
Any string that is meaningful to you; Currently not used by ABLE code. |
CryptographyAlgorithm |
The name of a cryptography
algorithm available in your Java environment. The default is |
CryptographyProvider |
The provider of the cryptography algorithm specified above.
|
Principal.N.Alias |
Any string that you will use to tie this principal to other configuration entries. For example,
|
Principal.N.Principal |
An actual KERBEROS principal. For example,
|
In a secure platform, trust levels
can be established for each specified principal. Trust levels are completely
arbitrary and you can define as many as you wish, giving them whatever names
you wish. TrustLevel.1
is the most trusted; TrustLevel.2
is less trusted than TrustLevel.1
,
and so on. When security is on, trust levels are examined in order to
determine whether requests from one agent or service to another are to be
honored. This can help prevent less trustworthy agents from doing bad things
to agents running in your critical Agent Pools.
TrustLevel.N.Alias |
Any string that you will use to tie this trust level definition to other configuration entries. For example,
|
TrustLevel.N.Principals |
A comma delimited list of principal aliases that you defined with Principal.N.Alias entries. For example,
|
You must define the Agent Pools (Java
Virtual Machines, or JVMs) that will contain your agents and agent services.
The Agent Pools can be scattered across a network, and each Agent Pool, through
association to a Principal, can be running with a different authorization and
security level. Later on, you can allocate agents and services to these Agent
Pools, perhaps placing less trustworthy agents in Agent Pools that are
restricted in some way, while trusted agents are placed into Agent Pools that
are completely unrestricted. (You may want to become familiar with the java.policy
file and how it can be used
to restrict Java code.)
AgentPool.N.Alias |
Any string that you will use to tie this Agent Pool definition to other configuration entries. For example,
|
AgentPool.N.IpAddress |
The IP address of the system where this Agent Pool is
running. The address may be given as a name (e.g.
|
AgentPool.N.Port |
The port on which this Agent Pool will be listening. See also Setting Java Permissions. Example:
|
AgentPool.N.Principal |
The alias of a principal that you defined as described in the section Principals. In a secure platform, the specified principal must be the one who starts the Agent Pool. Example:
|
The ABLE platform requires that certain
agent services always be available whenever the platform is available. The Services
statement identifies all
services that you want to run when your platform is started. You can add your
own services to the list, and then, for each service you add, enter another
statement that specifies where (in which Agent Pool) the service is to run, and
what Java class knows how to create the service (this is known as the factory).
If you write your own service, you should derive it from AbleBasicService
.
Services |
However, if you are creative, you can replace any service with one of your own (specify the fully qualified class name of the service’s factory) or add additional services to the platform. The following services are required: Agent-Directory-Service Agent-Naming-Service Message-Transport-System Agent-Life-Cycle-Service The following service is optional: Persistence-Service |
Agent-Directory-Service Agent-Naming-Service Message-Transport-System Agent-Life-Cycle-Service |
You do not need to change anything on these statements except to specify in which Agent Pool each service runs. You can place all services in one Agent Pool or place each service in its own Agent Pool. You can specify whether persistence is on or off, but if on, the Persistence-Service must be running. You can replace an IBM supplied service with one of your own, but this is not recommended. If security is on and you replace a service, your platform will not operate correctly. |
Agent Lifecycle Service will create only those agents that have been identified to it. You identify agents to the Agent Lifecycle Service by entering information about them into your preferences file. For each agent, you specify where (in which Agent Pools) the agent is allowed to run, and who is allowed to create and run it.
Agent.N.Alias |
Any string that you will use to tie this agent definition to other configuration entries. For example,
|
Agent.N.AutonomyLevel |
Any string that is meaningful to you; not used by ABLE code; for documentation only. |
Agent.N.ClassName |
The fully qualified Java class name of the agent. For
example, if you work for the Binford Tool Company and you have an agent
implemented by com.binford.tools.SpiffyAgent This class must be in the Java class path whenever the Agent Lifecycle Service is asked to create an agent of this type. |
Agent.N.ConstructorArgs |
Either empty or a comma delimited list of arguments to pass to the agent’s constructor method when the agent is created by the Agent Lifecycle Service. Each argument is of the form
where
|
Agent.N.EligiblePrincipals |
A comma delimited list of Principal aliases that you have defined (see Principals) and specifies those principals that are allowed to run the agent. The agent will inherit the trust level (see Trust Levels) associated with whichever principal starts the agent. |
Agent.N.EligibleAgentPools |
A comma delimited list of Agent Pool
aliases that you defined (see Agent Pools) and
specifies which of those Agent Pools the agent is allowed
to run in. For example, if you defined Agent.1.EligibleAgentPools=Pool-1,Pool-3 which indicates that the Agent Lifecycle Service is
allowed to create instances of this agent in Agent Pools |
Agent.N.InitArgs |
Either empty or a comma delimited list of arguments to
pass to the agent’s initialization method when the agent is initialized by
the Agent Lifecycle Service. The format of this statement is exactly the same
as for the |
Agent.N.LastChangedDate |
Any string that is meaningful to you; not used by ABLE code; for documentation only. |
Agent.N.Type |
Any string that is meaningful to you; not used by ABLE code; for documentation only. |
Agent.N.Vendor |
Any string that is meaningful to you; not used by ABLE code; for documentation only. |
Agent.N.Version |
Any string that is meaningful to you; not used by ABLE code; for documentation only. |
Before you can start up the platform
you may need to modify your java.policy
file to allow communications on the ports you configured in your ableplatform.preferences
file. To do
this, find the java.policy
file that is supplied with your Java installation and, using any text editor,
add lines similar to the following to it.
permission java.net.SocketPermission "tools.binford.com:55551-55553", "connect,accept,listen,resolve";
permission java.net.SocketPermission "9.10.84.20:55551-55553", "connect,accept,listen,resolve";
permission java.net.SocketPermission "localhost:55551-55553", "listen,resolve";
If you need to, add a statement for every system that you
have specified in your ableplatform.preferences
file, and the port numbers you enter here must also match the ports you
specified there.
You can use the java.policy
file together with the java.security
file to restrict the capabilities of each Java VM (Agent Pool) you have defined
in your preference file.
Before starting the platform, check
that, for every system identified in your ableplatform.preferences
file, you have
· Installed ABLE
· Made all necessary JARs and classes available to the Java runtime environment
·
Placed a copy of your updated ableplatform.preferences
on the system, either in the place specified by the ABLE_HOME
environment variable, or
in ABLE’s bin
directory.
·
Modified your java.policy
file, if needed
· If security is on, you will need a valid Kerberos ticket for the principal you log in as
Now you are able to start the platform. Again, for every
system that will participate in the platform, you will need to open a command
prompt, change to ABLE’s bin
directory and run the startPlatform
command, supplying as a parameter the port number used on that particular
system. If a system will run more than one JVM, each with a separate port
number, you will need to open additional command prompts and run the startPlatform
command specifying the
separate port number each time. For example, in two separate command windows
you will run one of the example commands below:
startPlatform 55551
startPlatform 55552
NOTE that before you start any other JVM you must first start the JVM that will contain the Agent Naming Service.
Once all bits of the platform are up and running, you can start your agents running.
The following example code, an agent derived from AblePlatformDefaultAgent, shows how to interact with the platform’s agent services.
//==================================================================== // Imports //==================================================================== import java.rmi.RemoteException; import java.util.Vector; import javax.agent.AgentName; import javax.agent.JasConstants; import javax.agent.Locator; import javax.agent.service.directory.AgentDescription; import com.ibm.able.*; import com.ibm.able.platform.*; /** * ExampleAgent is a self-initializing agent that interacts with Agent * Lifecycle Service to create, in various agent pools, instances of * agents defined in the <code>ableplatform.preferences</code> file. * */ public class ExampleAgent extends AblePlatformDefaultAgent implements AblePlatformServiceEventListener { |
/** * Creates, configures, and initializes an ExampleAgent agent that * shows how to connect to the Agent Lifecycle Service and create * and initialize an instance of another agent in some other agent * pool. */ public ExampleAgent() throws RemoteException { // --------------------------------------------------------------- // IF SECURITY IS ON, and because this is an agent that runs from // the command line outside of a platform Agent Pool, this agent // must: // o Specify which of the agent pools to associate with // o Set the principal that is running this agent/agent pool // --------------------------------------------------------------- if ( Able.Preferences.isSecure() ) { AblePlatform.Preferences.setLocalAgentPool("AgentPool1"); myPrincipal = "al/borland.binford.com@KERB5REALM.BINFORD.COM"; } // Configure this instance. Only a few agent attributes are shown, // but many more can be changed to control the agent's behavior. setName ("ExampleAgent"); setComment ("Example agent program."); setAgentSummary (new AbleMessageContainer("Just getting initialized")); setAgentType ("Binford_Agent"); addJasAgentAttribute("some-arbitrary-user-attribute", "attribute 1 value"); addJasAgentAttribute("another-arbitrary-user-attribute", "attribute two value"); // Initialize this instance. Note that when this method completes, // the agent will have obtained a unique AgentName from the Agent // Naming Service and will have registered an AgentDescription // with the Agent Directory Service. The agent is ready to receive // JAS Transport Messages, ABLE events, and do whatever else it is // configured to do. _init(); } // This agent registers as a platform Service Event Listener with // several agent services. When those services send out Service // Events, this method is where they will appear. This method is // from the AblePlatformServiceEventListener interface. public void handleAblePlatformServiceEvent(AblePlatformServiceEvent theServiceEvent) throws RemoteException { System.out.println("\n ExampleAgent received a platform service event: <" + theServiceEvent.getEventDescription() + ">. \n\n"); } /** * If security is on, this local helper method generates an * AbleSecureKey to be used on a subsequent secure method * call. Otherwise, null is returned. "mySecSppt" is an * AbleSecuritySupport object built-in to the * AblePlatformDefaultAgent, but the object is null unless security * is on. */ private AbleSecureKey secureKeyOrNull() throws Exception { return ((Able.Preferences.isSecure())? mySecSppt.generateKey() : null); } |
//================================================================== // // Mainline logic // // This method uses Agent Lifecycle Service to create an instance of // an agent defined in the ableplatform.preferences file. The agent // is created in its first eligible agent pool, and with overridden // constructor parameters. // // The Agent Directory Service is used to locate the agent, and the // Message Transport System is used to send the agent a message. // // Remote methods are invoked on the agent, and the Agent Lifecycle // Service is used to terminate the agent. // //================================================================== private void runExampleAgent() throws Exception { AgentLifeCycleService agentLifecycleService = null; AbleAgentClassDescription[] agentsDefinedInPreferences = null; // AblePlatformDefaultAgent has built-in access to the Agent // Naming Service, the Agent Directory Service, and the Message // Transport System, but not the Agent Lifecycle Service, so // acquire the Agent Lifecycle Service from the JAS Service Root. agentLifecycleService = (AgentLifeCycleService)myJasServiceRoot.getService (com.ibm.able.platform.AgentLifeCycleService.SERVICE_TYPE); if (agentLifecycleService==null) return; // Simply return on any error // Register as a service event listener with some of the agent services. agentLifecycleService.addAblePlatformServiceEventListener(this); myJasVerifiableDirectoryService.addAblePlatformServiceEventListener(this); myJasVerifiableNamingService.addAblePlatformServiceEventListener(this); // Ask Agent Lifecycle Service for a list of agents that have been // defined in the ableplatform.preferences file. These are the // agents that Lifecycle is permitted to create. agentsDefinedInPreferences = agentLifecycleService.getPermittedAgents(); if (agentsDefinedInPreferences==null) return; // In all the agents defined in preferences, look for an agent // defined with the alias of "TimAgent". This is the agent to // create later. String agentAlias = "TimAgent"; AbleAgentClassDescription foundAgentClassDescription = null; for (int i=0; i<agentsDefinedInPreferences.length; i++) { AbleAgentClassDescription testAgentClassDescription = agentsDefinedInPreferences[i]; String testAgentAlias = testAgentClassDescription.getAgentAlias(); if ( agentAlias.equals(testAgentAlias) ) { foundAgentClassDescription = testAgentClassDescription; break; } } if (foundAgentClassDescription==null) return; // A preference description of the "TimAgent" was found. Save // information about the agent; for example, find what Agent Pools // the agent is allowed to run in, and select the first Agent Pool. Vector eligibleAgentPools = foundAgentClassDescription.getEligibleAgentPools(); AblePlatformPreferences.AgentPoolEntry_ agentPoolInfo = (AblePlatformPreferences.AgentPoolEntry_)eligibleAgentPools.elementAt(0); String agentPoolAlias = agentPoolInfo.getAgentPoolAlias(); // The constructor of the (fictitious) TimAgent takes 3 // parameters: a String (which will become the agent's display // name), a Long, and a Double. The ableplatform.preferences file // supplies default values for these parameters, but they are // overridden here with new values. String agentDisplayName = "DisplayNameOfTimAgent"; Vector newCtorArgs = new Vector(); newCtorArgs.addElement(new String (agentDisplayName)); newCtorArgs.addElement(new Long (3000L)); newCtorArgs.addElement(new Integer(Able.ProcessingEnabled_PostingEnabled)); foundAgentClassDescription.setConstructorArgs(newCtorArgs); // Have the Agent Lifecycle Service create and intialize an // instance of the TimAgent. AblePlatformAgent timAgent = agentLifecycleService.createAgentInstanceAndInitialize(foundAgentClassDescription, agentPoolAlias, secureKeyOrNull()); // Use the Agent Directory Service to look up the agent just // created. ("lookupAgent" is a built-in method in // AblePlatformDefaultAgent that uses the Agent Directory // Service.) Save the newly created agent's unique AgentName and // also all its JAS Locators. AgentDescription[] timAgent_AgentDescription = lookUpAgent(JasConstants.AGENT_DISPLAY_NAME, agentDisplayName); AgentName timAgent_AgentName = timAgent_AgentDescription[0].getAgentName(); Locator[] timAgent_LocatorList = timAgent_AgentDescription[0].getLocators(); // Get the RMI Locator for the agent. This Locator, and the // AgentName saved above, will be used to send the agent a JAS // Transport Message. Locator timAgent_RmiLocator = null; for (int i=0; i<timAgent_LocatorList.length; i++) { if ( timAgent_LocatorList[i].getType().equalsIgnoreCase("rmi") ) { timAgent_RmiLocator = timAgent_LocatorList[i]; break; } } // Send the agent a JAS Transport Message. Again, the method is a // built-in of AblePlatformDefaultAgent. Note that instead of a // String, any Java object can be sent as a "message". sendTransportMessage(timAgent_RmiLocator, timAgent_AgentName, "Hello, Tim Agent"); // Becaue AblePlatformDefaultAgents are remote objects, their // methods can be accessed directly. Here, events are sent to the // agent, and the agent is suspended and resumed. timAgent.handleAbleEvent( new AbleEvent(this, null) ); // Send the agent an event timAgent.suspendAgent( secureKeyOrNull() ); // Suspend the agent for (int i=0; i<20; i++) {timAgent.handleAbleEvent( new AbleEvent(this, null) );}// Queue up many events for the agent timAgent.resumeAgent( secureKeyOrNull() ); // Resume the agent // Use the Agent Directory Service to find the agent again, and // once found, use the Agent Lifecycle Service to terminate the // agent. Note that the agent's "quit" method can be called // directly, just like the suspend and resume methods above, but // for demonstration purposes Lifecycle is used. AgentDescription foundAgentDescription[] = lookUpAgent(JasConstants.AGENT_DISPLAY_NAME, agentDisplayName); agentLifecycleService.quitAgent(foundAgentDescription[0], secureKeyOrNull()); // Deregister as a service event listener with the same services agentLifecycleService.removeAblePlatformServiceEventListener(this); myJasVerifiableDirectoryService.removeAblePlatformServiceEventListener(this); myJasVerifiableNamingService.removeAblePlatformServiceEventListener(this); } |
//================================================================== // // MAIN Method // //================================================================== /** * Creates an instance of an ExampleAgent agent, and calls its * mainline routine, runExampleAgent(). * * @param args * are all ignored.... */ public static void main(String args[]) { // Start ABLE's message and trace logging Able.startMessageAndTraceLogging(); // Kickoff an instance of this agent. ExampleAgents are // self-customizing and self-initializing, which means that when // the constructor is finished, the agent instance will have a // unique AgentName and will have registered its AgentDescription // with the Agent Directory Service. try { ExampleAgent anExampleAgent = new ExampleAgent(); anExampleAgent.runExampleAgent(); } catch(Exception theException) { theException.printStackTrace(); System.out.println("\n++ExampleAgent.main(): Caught exception: " + theException); } System.exit(0); } } |