/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ha.hasessionstate.server;
import java.util.ArrayList;
import java.util.Iterator;
import org.jboss.ha.framework.interfaces.SubPartitionInfo;
import org.jboss.ha.framework.interfaces.SubPartitionsInfo;
/**
* Default implementation of HASessionStateTopologyComputer
*
* @see org.jboss.ha.hasessionstate.interfaces.HASessionState
* @author sacha.labourey@cogito-info.ch
* @version $Revision: 81001 $
*
* <p><b>Revisions:</b><br>
*/
public class HASessionStateTopologyComputerImpl implements HASessionStateTopologyComputer
{
protected long nodesPerSubPartition = 0;
protected String sessionStateIdentifier = null;
/** Creates new HASessionStateTopologyComputerImpl */
public HASessionStateTopologyComputerImpl ()
{
}
public void init (String sessionStateName, long nodesPerSubPartition)
{
this.sessionStateIdentifier = sessionStateName;
this.nodesPerSubPartition = nodesPerSubPartition;
}
public void start () {}
public SubPartitionsInfo computeNewTopology (SubPartitionsInfo currentTopology, ArrayList newReplicants)
{
if (newReplicants.size () < 1)
currentTopology.partitions = null;
else if (newReplicants.size () == 1)
{
// we are alone! Are we already in a partition? If this is the case, we do not change!
//
if (currentTopology.partitions != null)
currentTopology = computeCompatibleComposition (currentTopology, newReplicants);
else
{
SubPartitionInfo aPartition = new SubPartitionInfo ();
aPartition.subPartitionName = getSubPartitionName (currentTopology);
aPartition.memberNodeNames.add (newReplicants.get (0));
SubPartitionInfo[] thePartition =
{ aPartition };
currentTopology.partitions = thePartition;
}
}
else if (currentTopology == null || currentTopology.partitions == null)
// this is the first time we will have to decide of a spliting
//
currentTopology = computerFirstComposition (currentTopology, newReplicants);
else
// There is a spliting already in place: we will need to take care of it in order to minimize group changes
// i.e. state transfer that will occur from these changes
//
currentTopology = computeCompatibleComposition (currentTopology, newReplicants);
return currentTopology;
}
protected SubPartitionsInfo computerFirstComposition (SubPartitionsInfo splitingInfo, ArrayList replicants)
{
int i=0;
String rep = null;
ArrayList newConfig = new ArrayList ();
SubPartitionInfo aPartition = null;
int grpNumber = 0;
// Build groups sequentially
//
for (Iterator reps = replicants.iterator (); reps.hasNext (); i++)
{
rep = (String)reps.next ();
if ( (i%nodesPerSubPartition) == 0 )
{
grpNumber++;
aPartition = new SubPartitionInfo ();
aPartition.subPartitionName = getSubPartitionName (splitingInfo);
newConfig.add (aPartition);
}
aPartition.memberNodeNames.add (rep);
}
// we don't like singleton nodes for HA...
//
if (aPartition.memberNodeNames.size () == 1)
{
rep = (String) aPartition.memberNodeNames.get (0); // get singleton info
newConfig.remove (grpNumber-1); // remove last singleton group
aPartition = (SubPartitionInfo)(newConfig.get (grpNumber-1)); // access last built group
aPartition.memberNodeNames.add (rep); // add singleton to last built group
}
SubPartitionInfo[] newSpliting = new SubPartitionInfo[1];
newSpliting = (SubPartitionInfo[]) newConfig.toArray (newSpliting);
splitingInfo.partitions = newSpliting;
return splitingInfo;
}
protected SubPartitionsInfo computeCompatibleComposition (SubPartitionsInfo splitingInfo, ArrayList replicants)
{
// In a first step, we purge the current spliting to remove dead members
//
SubPartitionInfo[] newSpliting = null;
ArrayList newSubParts = new ArrayList ();
for (int i=0; i<splitingInfo.partitions.length; i++)
{
SubPartitionInfo currentSubPart = splitingInfo.partitions[i];
SubPartitionInfo newCurrent = null;
Iterator iter = currentSubPart.memberNodeNames.iterator ();
while (iter.hasNext ())
{
String node = (String)iter.next ();
if (replicants.contains (node))
{
if (newCurrent == null)
{
newCurrent = (SubPartitionInfo)currentSubPart.clone ();
newCurrent.memberNodeNames.clear ();
}
newCurrent.memberNodeNames.add (node);
}
}
if (newCurrent != null)
newSubParts.add (newCurrent);
}
// we now create a list of new nodes that are not yet part of any group
//
Iterator iter = replicants.iterator ();
ArrayList newMembersNotInAGroup = new ArrayList ();
while (iter.hasNext ())
{
boolean found = false;
String aMember = (String)iter.next ();
Iterator iterNewSubPart = newSubParts.iterator ();
while (iterNewSubPart.hasNext () && !found)
if (((SubPartitionInfo)iterNewSubPart.next ()).memberNodeNames.contains (aMember))
found = true;
if (!found)
newMembersNotInAGroup.add (aMember);
}
iter = null;
// we now have purged our current sub-partition structure from its dead members
// we now check if some sub-partitions need to be merged to remove singleton groups
// or if there is a group with n>(nodesPerSubPartition) that may be reduced to its ideal size
//
// we remove elements that are less than the group size and put them in a new sorted list
//
ArrayList smallerGroups = new ArrayList ();
ArrayList correctlySizedGroups = new ArrayList ();
ArrayList biggerGroups = new ArrayList ();
for (int i=0; i<newSubParts.size (); i++)
{
int groupSize = ((SubPartitionInfo)newSubParts.get (i)).memberNodeNames.size ();
if (groupSize < this.nodesPerSubPartition)
smallerGroups.add (newSubParts.get (i));
else if (groupSize > this.nodesPerSubPartition)
biggerGroups.add (newSubParts.get (i));
else
correctlySizedGroups.add (newSubParts.get (i));
}
// for our algo, we need to sort smallerGroups
//
java.util.Collections.sort (smallerGroups);
//
// Our algo is not perfect and could, for example, take in account, the actual group load in order to minimize
// the synchronization time
//
// 1st step: we place newly started nodes (not yet part of a group) in smallerGroups
// by first feeding small groups
//
iter = newMembersNotInAGroup.iterator ();
while (iter.hasNext ())
{
String member = (String)iter.next ();
SubPartitionInfo target = null;
if (smallerGroups.size () > 0)
{
target = (SubPartitionInfo)smallerGroups.get (0); // array is sorted
target.memberNodeNames.add (member);
if (target.memberNodeNames.size () == this.nodesPerSubPartition)
{
// we have a complete sub-partition, we change its owning group
//
smallerGroups.remove (0);
correctlySizedGroups.add (target);
}
}
else
{
// we create an singleton group
//
target = new SubPartitionInfo ();
target.setIsNewGroup ();
target.subPartitionName = getSubPartitionName (splitingInfo);
target.memberNodeNames.add (member);
smallerGroups.add (target);
java.util.Collections.sort (smallerGroups);
}
}
// 2nd step: we reduce the size of any too-big sub-partition (biggerGroups)
// by removing the last component and feeding elements in smallerGroups
// If smallerGroups is empty, we don't modify biggerGroups (minimize
// involved state transfer)
//
iter = biggerGroups.iterator ();
while (iter.hasNext ())
{
SubPartitionInfo big = (SubPartitionInfo)iter.next ();
if (smallerGroups.size () > 0)
{
String member = (String)big.memberNodeNames.get (big.memberNodeNames.size ()-1); // get last one
SubPartitionInfo target = null;
target = (SubPartitionInfo)smallerGroups.get (0); // array is sorted
target.memberNodeNames.add (member);
big.memberNodeNames.remove (big.memberNodeNames.size () -1);
if (target.memberNodeNames.size () == this.nodesPerSubPartition)
{
// we have a complete sub-partition, we change its owning group
//
smallerGroups.remove (0);
correctlySizedGroups.add (target);
}
}
}
// biggerGroups is now processed, we can move it to the correctly sized group
//
correctlySizedGroups.addAll (biggerGroups);
// 3rd step: we now try to merge sub-partitions belonging to smallerGroups to form bigger groups (up to the
// max size of a sub-partition). We travel in descending order to keep max granularity when forming groups
//
boolean thirdStepFinished = (smallerGroups.size () == 0);
while (!thirdStepFinished)
{
//thirdStepFinished = (smallerGroups.size () == 0);
SubPartitionInfo current = (SubPartitionInfo)smallerGroups.get (smallerGroups.size ()-1);
for (int i = smallerGroups.size ()-2; i >= 0; i--)
{
// test if the merge is possible
//
SubPartitionInfo merger = (SubPartitionInfo)smallerGroups.get (i);
if ((merger.memberNodeNames.size () + current.memberNodeNames.size ()) <= this.nodesPerSubPartition)
{
// it is possible to merge both
//
current.merge (merger);
smallerGroups.remove (i);
}
// we check if we need to go further or not
//
if (current.memberNodeNames.size () == this.nodesPerSubPartition)
break;
}
if (current.memberNodeNames.size () > 1)
{
// we only move non-singleton groups
//
smallerGroups.remove (smallerGroups.size ()-1);
correctlySizedGroups.add (current);
}
thirdStepFinished = ( (smallerGroups.size () == 0) ||
((smallerGroups.size () == 1) && ( ((SubPartitionInfo)smallerGroups.get (0)).memberNodeNames.size () == 1)) );
}
// 4th step: if smallerGroups is not empty, it means that we have a singleton. In that case,
// we merge it with the smallest group we can find.
//
if (smallerGroups.size () > 0)
{
if (correctlySizedGroups.size ()>0)
{
java.util.Collections.sort (correctlySizedGroups);
SubPartitionInfo merger = (SubPartitionInfo)smallerGroups.get (0);
SubPartitionInfo master = (SubPartitionInfo)correctlySizedGroups.get (0);
master.merge (merger);
}
else
{
// we have a single singleton group!
//
correctlySizedGroups.add (smallerGroups.get (0));
}
}
// we now commit our new splitting. All members will consequently receive a message indicating
// that the spliting has changed and act accordingly
//
newSpliting = new SubPartitionInfo[1];
newSpliting = (SubPartitionInfo[])correctlySizedGroups.toArray (newSpliting);
splitingInfo.partitions = newSpliting;
return splitingInfo;
}
protected String getSubPartitionName (SubPartitionsInfo manager)
{
return this.sessionStateIdentifier + "-Group-" + manager.getNextGroupId ();
}
// testing of the above algo... can be commented...
//
/*
public static void main (String[] args)
{
HASessionStateTopologyComputerImpl tmp = new HASessionStateTopologyComputerImpl ();
tmp.init ("test", 2);
tmp.start ();
SubPartitionsInfo splitInfo = new SubPartitionsInfo ();
ArrayList replic = new ArrayList ();
ArrayList parts = new ArrayList ();
splitInfo.partitions = new SubPartitionInfo[1];
//parts.add (new SubPartitionInfo ("SP1", helper_String ("ABC")));
//parts.add (new SubPartitionInfo ("SP2", helper_String ("DEF")));
//parts.add (new SubPartitionInfo ("SP3", helper_String ("GHI")));
parts.add (new SubPartitionInfo ("SP4", helper_String ("AB")));
parts.add (new SubPartitionInfo ("SP5", helper_String ("EF")));
//parts.add (new SubPartitionInfo ("SP1", helper_String ("Axy")));
//parts.add (new SubPartitionInfo ("SP2", helper_String ("Bmn")));
//parts.add (new SubPartitionInfo ("SP3", helper_String ("CDE")));
//parts.add (new SubPartitionInfo ("SP4", helper_String ("FGHI")));
splitInfo.partitions = (SubPartitionInfo[])parts.toArray (splitInfo.partitions);
replic = new ArrayList (java.util.Arrays.asList (helper_String ("AEF") ));
System.out.println (replic);
System.out.println (splitInfo);
splitInfo = tmp.computeCompatibleComposition (splitInfo, replic);
System.out.println (splitInfo);
}
private static String[] helper_String (String letters)
{
String[] tabl = new String[letters.length ()];
for (int i=0; i<letters.length ();i++)
tabl[i]=letters.substring (i, i+1);
return tabl;
}
*/
}