Mobile Agents And Migration

11.1. Migration Overview

Active Objects have the ability to migrate between nodes while the application is running. Whatever application was using the active objects will continue running regardless of the migration of active objects.

The migration of an active object can be triggered by the active object itself, or by an external agent. In both cases a single method will eventually get called to perform the migration. It is the method migrateTo(...) accessible from a migratable body (a body that inherits from MigratableBody).

In order to ease the use of the migration, ProActive provides 2 sets of static methods in the PAMobileAgent class. The first set is aimed at the migration triggered from the active object that wants to migrate. The methods rely on the fact that the calling thread is the active thread of the active object:

  • PAMobileAgent.migrateTo(Object o): migrate to the same location as an existing active object

  • PAMobileAgent.migrateTo(String nodeURL): migrate to the location given by the URL of the node

  • PAMobileAgent.migrateTo(Node node): migrate to the location of the given node

The second set is aimed at the migration triggered from another agent than the target active object. In this case the external agent must have a reference to the Body of the active object it wants to migrate. The boolean value in the following methods indicates if the migration modifies the application's computation.

  • PAMobileAgent.migrateTo(Body body, Object o, boolean isNFRequest): migrate to the same location as an existing active object

  • PAMobileAgent.migrateTo(Body body, String nodeURL, boolean isNFRequest): migrate to the location given by the URL of the node

  • PAMobileAgent.migrateTo(Body body, Node node, boolean isNFRequest): migrate to the location of the given node

  • PAMobileAgent.migrateTo(Body body, Node node, boolean isNFRequest, int priority): migrate to the location of the given node The int value indicates the priority of the non functional request. The levels are defined in the Request interface.

11.2. Using Migration

Any active object has the ability to migrate. If it references some passive objects, they will also migrate to the new location. Since we rely on the serialization to send the object on the network, the active object must implement the serializable interface. To migrate, an active object must have a method which contains a call to PAMobileAgent.migrateTo(...). This call must be the last one in the method, i.e the method must return immediately after. Here is an example of a method in an active object:

public void moveTo(String t) {
   try {
      PAMobileAgent.migrateTo(t);
    } catch (Exception e) {
       e.printStackTrace();
    }
}

We don't provide any test to check if the call to migrateTo is the last one in the method, hence if this rule is not enforced, it can lead to unexpected behavior. To make this object migrate, you just have to call its moveTo(String) method.

11.3. Complete example

import org.objectweb.proactive.ProActive;
public class SimpleAgent implements Serializable {
   public SimpleAgent() {} //empty, no-arg constructor required by ProActive
   public void moveTo(String t) {
      try {
         PAMobileAgent.migrateTo(t);
      } catch (Exception e) {
         e.printStackTrace();
      }
    }
    public String whereAreYou() {
      try {
         return InetAddress.getLocalHost().getHostName();
      } catch (Exception e) {
         return 'Localhost lookup failed';
      }
    }
    public static void main (String[] args) {
      if (!(args.length<0)) {
        System.out.println('Usage: java migration.test.TestSimple hostname/NodeName ');
        System.exit(-1);
      }
      SimpleAgent t = null;
      try {
        // create the SimpleAgent in this JVM
        t = (SimpleAgent) ProActive.newActive('migration.test.SimpleAgent',null);
      } catch (Exception e) {
        e.printStackTrace();
      }
      // migrate the SimpleAgent to the location identified by the given node URL
      // we assume here that the node does already exist
      t.moveTo(args[0]);
      System.out.println('The Active Object is now on host ' + t.whereAreYou());
    }
}

The class SimpleAgent implements Serializable so the objects created will be able to migrate. We need to provide an empty constructor to avoid side effects during the creation of active objects. This object has two methods, moveTo() which makes it migrate to the specified location, and whereAreYou() which returns the hostname of the new location of the agent.

In the main method, we first need to create an active object, which is done through the call to newActive(). Once this is done, we can call methods on it as on any object. We call its moveTo method which will make it migrate to the node specified as parameter and then we ask it what is its current location. For our application we pass the location of the node as runtime parameter to the application.

package active;
import java.io.IOException;

public class Main{
	public static void main(String args[])
	{
		try{
			SimpleAgent ao = (SimpleAget)PAActiveObject.newActive(
					MigratableHello.class.getName(),
		            new Object [] {},
		            null);
		//the first parameter past to the program is the node location
		ao.moveTo(args[0]);
		}
		catch (ActiveObjectCreationException aoExcep){
			System.err.println(aoExcep.getMessage());
		}
	}
}

To see another example of migration look at the migration example in Chapter 6, Active Object Tutorial.

11.4. Dealing with non-serializable attributes

Implementing a MigrationStrategy (specifying what to do on departure and on arrival).

The migration of an active object uses serialization. Unfortunately, not all the objects in the Java language are serializable so the first step to solve the problem is to identify the unserializable components. In front the declaration of unserializable fields we will put the keyword transient. This indicates that the value of this variable should not be serialized. After the first migration, these fields will be set to null since they have not been saved. So we have to explicitly rebuild them upon arrival of the active object at its new location. This can be done by using the MigrationStrategyManager interface or by implementing readObject(java.io.ObjectInputStream in) and writeObject(java.io.ObjectOutputStream out) from the interface Serializable.

11.4.1.  Using The MigrationStrategyManager Interface

We can use the methods onArrival(String) and onDeparture(String) to specify which methods will be run when the active objects migrates.

The arguments for onArrival(String) and onDeparture(String) are the methods to be run before serialization and after deserialization. We create a new MigrationStrategyManagerImpl for the body of the object to be migrated and assign the methods to be run. For this we use the initActivity(Body) method because we only need to run the creation and assignment once.

public class SomeWhatUnserializable implements MigrationStrategyManager {
   private SerializableType saved; //will store the values 
   private transient UnserializableType toBeSaved; //will be null after serialization
   public void initActivity(Body body) {
	   .....   
	   MigrationStrategyManagerImpl myStrategyManager = new MigrationStrategyManagerImpl(
	       org.objectweb.proactive.core.body.migration.Migratable) body);
	       myStrategyManager.onArrival("rebuild");
	       myStrategyManager.onDeparture("leaveHost");
	}
	
	public void rebuild(){
		toBeSaved = saved.getValues();
		//other rebuild actions 
	}
	public void leaveHost(){
		saved = toBeSaved.getValues(); //saves the value of the unserializable type
		//other save actions
	}
} 

11.4.2.  Using readObject(...) and writeObject(...)

Another way of dealing with unserializable attributes is using the methods from the Serializable interface:

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
 private void readObjectNoData() 
     throws ObjectStreamException;

We override these methods to provide a way to save and restore the non-serializable components of the active object:

public class SomeWhatUnserializable implements MigrationStrategyManager {
	private transient UnserializableType toBeSaved; //will be null after serialization
	
	private void writeObject(java.io.ObjectOutputStream out) 
		throws java.io.IOException {
		//do the default serialization
		out.defaultWriteObject();
		//do the custom serialization
		out.writeObject(toBeSaved.getValues());	    
	}
	
	private void readObject(java.io.ObjectInputStream in) 
		throws java.io.IOException, ClassNotFoundException {
		//do the default deserialization
		in.defaultReadObject(); 
		//do the custom deserialization
		toBeSaved = in.readObject();
	}
} 

To learn how to use the methods see the Serializable interface in the standard JavaDoc.

11.5. Mixed Location Migration

11.5.1. Forwarders And Agents

There are two way to communicate with an active object which has migrated :

  • Forwarders

    An active object upon leaving a site leaves behind a special object, a forwarder, which is in charge of forwarding incoming messages to the next destination. As time goes, a chain of forwarders builds between a caller and the mobile object. Any message sent to the latter will go through the chain to reach the agent. There is a virtual path between a caller and a mobile object. As soon as a call is peformed on the agent the chain of forwarders is shortened into a direct link to the agent called.

  • Location Server

    Communicating with a mobile object can be done with an explicit reference towards the mobile entity, which requires the means to get its current location when necessary.

    There is a two-step communication: first there should be a search to obtain an up-to-date reference (localization), and then the actual communication. The simplest solution is to have a unique location server which maintains a database of the known position of the agents. When an object wants to communicate with an object which has migrated, it queries the server which sends back a new reference. If this is a correct reference then the communication takes place, otherwise a new query is issued.

Both techniques have their drawbacks. Two problems arise when using a forwarding scheme, especially if the ambition is scalable mobile agents over WAN. First, the forwarders use resources on a site as long as they have not been garbage collected. Thus if a chain exists between to objects, it will remains even if there is no new communications going by. Second, the longer the chain is, the more likely it will be cut because of a hardware or software failure. As a consequence, while forwarders are more efficient under some conditions, they do not appear to be scalable, nor reliable.

The server on the other hand is a single point of failure and a potential bottleneck. If a server is to help communicating with a higher number of mobile agents, then it might not be able to serve requests quickly enough. Furthermore, in case of a crash, it is not possible to communicate with mobile active objects until the server is back. It is possible to avoid most of these issues by having redundant servers with load balancing at the cost of increasing complexity.

Based on these observations and taking into account the variability of the environment, we use a configurable communication protocol which offers the main benefits from both the forwarder and the server while avoiding their drawbacks. Configurable with discrete and continuous parameters, it can be tailored to the environment to offer both performance and reliability.

11.5.2. Forwarder And Agent Parameters

Forwarders and agents have several parameters which control their behaviour:

  • forwarder time to live in milliseconds - proactive.mixedlocation.ttl

    Time limited forwarders remain alive only for a limited period. When their lifetime is over, they can be removed. First of all, this brings an important advantage: scalability due to absence of the DGC and the systematic reclaim of forwarding resources. However this increases the risks of having the forwarding chain cut since a forwarder might expire during the normal execution of the application. In such a situation, we rely on a server which will be considered as an alternative solution. This increases the overall reliability.

    Use -1 to indicate that the forwarders have a unlimited lifetime.

  • forwarder updating status, boolean - proactive.mixedlocation.updatingForwarder

    It is possible to rely on the forwarders to maintain the location of the agent by having them update the server. When they reach the end of their lifetime, they can send to the server their outgoing reference which could be the address of the agent or another forwarder. The updatingForwarder parameter can be true or false. If true it releases the agent from most of the updates. In order to increase reliability, it is possible to have also the agent update the server on a regular basis. This value can be true or false.

  • agent time to update in milliseconds

    Each mobile agent has a nominal Time To Update (TTU) after which it will inform the localization server of its new location. The TTu is defined as the first occurence of two potential events since the last update:

    • agent migrations number, integer - proactive.mixedlocation.maxMigrationNb

      This parameter ndicates the number of migrations before updating the server. Use -1 to indicate that the agent never updates the server.

    • agent time on site in milliseconds - proactive.mixedlocation.maxTimeOnSite

      The parameter indicates the maximum time spent on a site before updating the server. You can use -1 to indicate that agent will not inform the server.

If we consider that both the agent and the forwarders can send updates to the server, the server must be able to make the difference between messages from the forwarders and from the agent; those are always the most up to date. Also, since we don't have any constraint on the Time To Live (TTL) of the forwarders, it could be that a forwarder at the beginning of a chain dies after on at the end. If this happens and we are not careful when dealing with the requests, the server could erase a more up to date reference with an old one.

11.5.3. Configuration File

As a default, ProActive uses a "Forwarder based" strategy . It means that the forwarders have a unlimited lifetime and the agent never updates the location server.

To configure your own strategy, you have to edit the file ProActive/src/Core/org/objectweb/proactive/core/config/ProActiveConfiguration.xml and change the four values listed above.

<?xml version="1.0" encoding="UTF-8"?>
<ProActiveUserProperties>
    <properties>
        <prop key="proactive.locationserver"
            value="org.objectweb.proactive.ext.locationserver.LocationServer" />
        <prop key="proactive.locationserver.rmi" value="//localhost/LocationServer" />
        <prop key="proactive.securitymanager" value="true" />
        <prop key="proactive.mixedlocation.ttl" value="-1" />
        <prop key="proactive.mixedlocation.updatingForwarder"
            value="false" />
        <prop key="proactive.mixedlocation.maxMigrationNb" value="-1" />
        <prop key="proactive.mixedlocation.maxTimeOnSite" value="-1" />
    
        <prop key="proactive.net.nolocal"   value="false" />
        <prop key="proactive.net.noprivate" value="false" />
      <prop key="proactive.useIPaddress" value="true" />
      
        <prop key="proactive.future.ac" value="true" />
        <prop key="proactive.stack_trace" value="false" /> <!-- complete stack traces in
 requests -->
        <prop key="proactive.dgc" value="false" />
        <prop key="proactive.exit_on_empty" value="false" /> <!-- destroy the JVM when there
 is no more active object -->
        <prop key="schema.validation" value="true" />
        <prop key="proactive.communication.protocol" value="rmi" />
        <prop key="proactive.rmi.port" value="1099" />
       
       <!--<prop key="proactive.java.policy"
 value="../../../../../../scripts/unix/proactive.java.policy"/>-->
        <prop key="fractal.provider" value="org.objectweb.proactive.core.component.Fractive" />
       <!-- <prop key="proactive.tunneling.connect_timeout" value="2000"/> -->
        <prop key="proactive.tunneling.try_normal_first" value="true" />
        <prop key="proactive.tunneling.use_gc" value="true" />
        <prop key="proactive.tunneling.gc_period" value="10000" />
        <prop key="proactive.classloader" value="false" />
    <!-- <prop key="proactive.ssh.known_hosts" value="/user/mlacage/home/.ssh/known_hosts"/>
 -->
    <!-- <prop key="proactive.ssh.key_directory" value="/user/mlacage/home/.ssh/"/> -->
    <!-- <prop key="proactive.ssh.port" value="22"/> -->
    <!-- <prop key="proactive.ssh.username" value="mlacage"/> -->
        <prop key="components.creation.timeout" value="10000" /> <!-- timeout in seconds for
 parallel creation of components -->

    <!-- ***************************************************** -->
    <!-- P2P PROPERTIES VALUE -->
    <!-- THESE ARE DEFAULT VALUES -->
    
    <!-- Acquisition method -->
        <prop key="proactive.p2p.acq" value="rmi" />
    <!-- Port number -->
        <prop key="proactive.p2p.port" value="6444" />
    <!-- NOA is in number of peers -->
        <prop key="proactive.p2p.noa" value="3" />
    <!-- TTU is in ms -->
        <prop key="proactive.p2p.ttu" value="60000" />
    <!-- TTL is in hops -->
        <prop key="proactive.p2p.ttl" value="5" />
    <!-- List capacity of message sequence number -->
        <prop key="proactive.p2p.msg_capacity" value="1000" />
    <!-- Percentage of agree responses -->
        <prop key="proactive.p2p.expl_msg" value="66" />
    <!-- Timeout for node acquisition -->
        <prop key="proactive.p2p.nodes_acq_to" value="180000" />
    <!-- Lookup frequency for nodes -->
        <prop key="proactive.p2p.lookup_freq" value="30000" />
    <!-- If true deploying one shared nodes by CPU, else only one node is shared -->
        <prop key="proactive.p2p.multi_proc_nodes" value="false" />
    <!-- ***************************************************** -->
    
    <!-- ***************************************************** -->
    <!-- MASTER/WORKER PROPERTIES VALUE -->
    <!-- Period of the slave pinging (in ms) -->
        <prop key="proactive.masterworker.pingperiod" value="10000" />
        <!-- Compressing tasks in the repository -->
        <prop key="proactive.masterworker.compresstasks" value="false" />
        

    <!-- Workaround to fix rmissh/unicore conflict -->
        <prop key="proactive.unicore.forkclient" value="true" />
        <prop key="proactive.runtime.stayalive" value="true" />    

    <!-- File Transfer Properties -->
        <prop key="proactive.filetransfer.services_number" value="16" />
        <prop key="proactive.filetransfer.blocks_number" value="8" />
        <prop key="proactive.filetransfer.blocks_size_kb" value="512" />
        <prop key="proactive.filetransfer.buffer_size_kb" value="256" />
    </properties>
    <javaProperties>
        <prop key="java.protocol.handler.pkgs" value="org.objectweb.proactive.core.ssh" />
        <prop key="ibis.name_server.host" value="localhost" />
        <prop key="ibis.name_server.key" value="1" />
        <prop key="ibis.io.serialization.classloader" value=
"org.objectweb.proactive.core.mop.MOPClassLoader" />
        <prop key="ibis.serialization" value="ibis" />
    </javaProperties>
</ProActiveUserProperties>

11.5.4. Location Server

A location server is available in the package org.objectweb.proactive.core.body.migration.MixedLocationServer. It can be launched using scripts/unix/migration/LocationServer. You can indicate on which node it have to be running.

Limitation : there can be only one LocationServer for the migration.