View Javadoc

1   /***
2    * 
3    * Copyright 2004 Protique Ltd
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License"); 
6    * you may not use this file except in compliance with the License. 
7    * You may obtain a copy of the License at 
8    * 
9    * http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License. 
16   * 
17   **/
18  package org.activecluster.group;
19  
20  import org.activecluster.Node;
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  
29  /***
30   * Represents a collection of zero or more groups in a cluster.
31   * The default implementation will create groups as nodes are added to the cluster; filling
32   * the groups with its required number of buddies / slaves until a new group can be created.
33   * <p/>
34   * Nodes which are not allowed to be master nodes will be kept around in a pool ready to be added
35   * as slaves when a new master arrives and forces the creation of a group.
36   *
37   * @version $Revision: 1.1 $
38   * @see Group
39   */
40  public class GroupModel {
41      private int maximumGroups = -1;
42      private int minimumMemberCount = 2;
43      private int maximumMemberCount = 3;
44      private List groups = new ArrayList();
45      private LinkedList incompleteGroups = new LinkedList();
46      private LinkedList completeGroups = new LinkedList();
47      private LinkedList fullGroups = new LinkedList();
48      private LinkedList unusedNodes = new LinkedList();
49      private NodeFilter masterFilter;
50      private Map nodeMemberships = new HashMap();
51  
52      // allow a node to be a master and 2 buddies
53      private int maximumWeighting = 10;
54  
55      /***
56       * Adds the new node to this group model; we assume the node has not been added before.
57       *
58       * @param node
59       */
60      public synchronized void addNode(Node node) {
61          if (!addToExistingGroup(node)) {
62              Group group = makeNewGroup(node);
63              if (group == null) {
64                  addToUnusedNodes(node);
65              }
66              else {
67                  addGroup(group);
68              }
69          }
70      }
71  
72      /***
73       * Removes the node from the group model
74       *
75       * @param node
76       */
77      public synchronized void removeNode(Node node) {
78          unusedNodes.remove(node);
79  
80          // lets remove the node from each group
81          for (Iterator iter = groups.iterator(); iter.hasNext();) {
82              Group group = (Group) iter.next();
83              boolean wasFull = group.isFull();
84              boolean wasUsable = group.isUsable();
85  
86              if (removeNodeFromGroup(group, node)) {
87                  updateGroupCollections(group, wasFull, wasUsable);
88              }
89          }
90      }
91  
92      // Properties
93      //-------------------------------------------------------------------------
94  
95      /***
96       * Returns a snapshot of the groups currently available
97       */
98      public synchronized List getGroups() {
99          return new ArrayList(groups);
100     }
101 
102     public NodeFilter getMasterFilter() {
103         return masterFilter;
104     }
105 
106     public void setMasterFilter(NodeFilter masterFilter) {
107         this.masterFilter = masterFilter;
108     }
109 
110     public int getMaximumGroups() {
111         return maximumGroups;
112     }
113 
114     public void setMaximumGroups(int maximumGroups) {
115         this.maximumGroups = maximumGroups;
116     }
117 
118     public int getMaximumMemberCount() {
119         return maximumMemberCount;
120     }
121 
122     public void setMaximumMemberCount(int maximumMemberCount) {
123         this.maximumMemberCount = maximumMemberCount;
124     }
125 
126     public int getMinimumMemberCount() {
127         return minimumMemberCount;
128     }
129 
130     public void setMinimumMemberCount(int minimumMemberCount) {
131         this.minimumMemberCount = minimumMemberCount;
132     }
133 
134     public int getMaximumWeighting() {
135         return maximumWeighting;
136     }
137 
138     public void setMaximumWeighting(int maximumWeighting) {
139         this.maximumWeighting = maximumWeighting;
140     }
141 
142 
143     // Implementation methods
144     //-------------------------------------------------------------------------
145 
146 
147     /***
148      * Attempt to make a new group with the current node as the master
149      * or if the node cannot be a master node
150      *
151      * @return the newly created group or false if none was created.
152      */
153     protected Group makeNewGroup(Node node) {
154         // no pending groups available so lets try and create a new group
155         if (canCreateGroup(node)) {
156             Group group = createGroup(node);
157             addNodeToGroup(group, node);
158             return group;
159         }
160         else {
161             return null;
162         }
163     }
164 
165     protected void tryToFillGroupWithBuddies(Group group) {
166         boolean continueFillingGroups = true;
167         while (!group.isUsable() && continueFillingGroups) {
168             continueFillingGroups = tryToAddBuddy(group);
169         }
170 
171         if (continueFillingGroups) {
172             // lets try fill more unfilled nodes
173             for (Iterator iter = new ArrayList(incompleteGroups).iterator(); iter.hasNext() && continueFillingGroups;) {
174                 group = (Group) iter.next();
175 
176                 boolean wasFull = group.isFull();
177                 boolean wasUsable = group.isUsable();
178 
179                 while (!group.isUsable() && continueFillingGroups) {
180                     continueFillingGroups = tryToAddBuddy(group);
181                 }
182 
183                 if (group.isUsable()) {
184                     updateGroupCollections(group, wasFull, wasUsable);
185                 }
186             }
187         }
188     }
189 
190     protected boolean tryToAddBuddy(Group group) {
191         boolean continueFillingGroups = true;
192         // TODO we could make this much faster using a weighting-sorted collection
193         NodeMemberships lowest = null;
194         int lowestWeight = 0;
195         for (Iterator iter = nodeMemberships.values().iterator(); iter.hasNext();) {
196             NodeMemberships memberships = (NodeMemberships) iter.next();
197             if (!memberships.isMember(group)) {
198                 int weighting = memberships.getWeighting();
199                 if ((lowest == null || weighting < lowestWeight) && weighting < maximumWeighting) {
200                     lowest = memberships;
201                     lowestWeight = weighting;
202                 }
203             }
204         }
205         if (lowest == null) {
206             continueFillingGroups = false;
207         }
208         else {
209             addNodeToGroup(group, lowest.getNode());
210         }
211         return continueFillingGroups;
212     }
213 
214     /***
215      * Lets move the group from its current state collection to the new collection if its
216      * state has changed
217      */
218     protected void updateGroupCollections(Group group, boolean wasFull, boolean wasUsable) {
219         boolean full = group.isFull();
220         if (wasFull && !full) {
221             fullGroups.remove(group);
222         }
223         boolean usable = group.isUsable();
224         if (wasUsable && !usable) {
225             completeGroups.remove(group);
226         }
227         if ((!usable || !full) && (wasFull || wasUsable)) {
228             incompleteGroups.add(group);
229         }
230     }
231 
232     protected void addToUnusedNodes(Node node) {
233         // lets add the node to the pool ready to be used if a node fails
234         unusedNodes.add(node);
235     }
236 
237     /***
238      * Attempts to add the node to an incomplete group, or
239      * a not-full group and returns true if its possible - else returns false
240      *
241      * @return true if the node has been added to a groupu
242      */
243     protected boolean addToExistingGroup(Node node) {
244         if (!addToIncompleteGroup(node)) {
245             if (!addToNotFullGroup(node)) {
246                 return false;
247             }
248         }
249         return true;
250     }
251 
252     protected boolean addToNotFullGroup(Node node) {
253         return addToPendingGroup(completeGroups, node);
254     }
255 
256     protected boolean addToIncompleteGroup(Node node) {
257         return addToPendingGroup(incompleteGroups, node);
258     }
259 
260     /***
261      * Adds the given node to the first pending group if possible
262      *
263      * @return true if the node was added to the first available group
264      */
265     protected boolean addToPendingGroup(LinkedList list, Node node) {
266         if (!list.isEmpty()) {
267             Group group = (Group) list.getFirst();
268             addNodeToGroup(group, node);
269             if (group.isFull()) {
270                 list.removeFirst();
271                 fullGroups.add(group);
272             }
273             else if (group.isUsable()) {
274                 list.removeFirst();
275                 completeGroups.add(group);
276             }
277             return true;
278         }
279         return false;
280     }
281 
282     protected void addNodeToGroup(Group group, Node node) {
283         NodeMemberships memberships = (NodeMemberships) nodeMemberships.get(node);
284         if (memberships == null) {
285             memberships = new NodeMemberships(node);
286             nodeMemberships.put(node, memberships);
287         }
288         memberships.addToGroup(group);
289     }
290 
291     protected boolean removeNodeFromGroup(Group group, Node node) {
292         NodeMemberships memberships = (NodeMemberships) nodeMemberships.get(node);
293         if (memberships != null) {
294             return memberships.removeFromGroup(group);
295         }
296         return false;
297     }
298 
299 
300     protected void addGroup(Group group) {
301         groups.add(group);
302         if (group.isFull()) {
303             fullGroups.add(group);
304         }
305         else if (group.isUsable()) {
306             completeGroups.add(group);
307         }
308         else {
309             incompleteGroups.add(group);
310         }
311     }
312 
313     protected Group createGroup(Node node) {
314         return new Group(minimumMemberCount, maximumMemberCount);
315     }
316 
317     /***
318      * Returns true if we can add a new group to the cluster
319      */
320     protected boolean canCreateGroup(Node node) {
321         return (maximumGroups < 0 || groups.size() < maximumGroups) && canBeMaster(node);
322     }
323 
324     /***
325      * Returns true if the given node can be a master
326      */
327     protected boolean canBeMaster(Node node) {
328         return masterFilter == null || masterFilter.evaluate(node);
329     }
330 }