View Javadoc

1   /*
2    * Copyright (c) 1998-2002 The Jgroup Team.
3    *
4    * This program is free software; you can redistribute it and/or modify
5    * it under the terms of the GNU Lesser General Public License version 2 as
6    * published by the Free Software Foundation.
7    *
8    * This program is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   * GNU Lesser General Public License for more details.
12   *
13   * You should have received a copy of the GNU Lesser General Public License
14   * along with this program; if not, write to the Free Software
15   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16   *
17   */
18  
19  package jgroup.upgrade;
20  
21  import java.rmi.NotBoundException;
22  import java.rmi.RemoteException;
23  
24  import jgroup.core.ExternalGMIListener;
25  import jgroup.core.ExternalGMIService;
26  import jgroup.core.JgroupException;
27  import jgroup.core.Layer;
28  import jgroup.core.MemberId;
29  import jgroup.core.MembershipListener;
30  import jgroup.core.MembershipService;
31  import jgroup.core.View;
32  import jgroup.core.arm.ExecException;
33  import jgroup.core.arm.UnknownGroupException;
34  import jgroup.core.protocols.Multicast;
35  import jgroup.core.registry.RegistryService;
36  import jgroup.relacs.config.AppConfig;
37  import jgroup.relacs.config.DistributedSystemConfig;
38  import jgroup.relacs.config.Host;
39  import jgroup.relacs.types.EndPointImpl;
40  import jgroup.relacs.types.ViewImpl;
41  
42  import org.apache.log4j.Logger;
43  
44  /**
45   *  The <code>UpgradeLayer</code> allows server replicas to be upgraded
46   *  with new software versions.
47   *
48   *  This layer implements the <code>RecoveryListener</code>, causing the
49   *  group manager construction to invoke the <code>initReplica</code>
50   *  method on the recovery layer so as to initialize the replica and
51   *  join the object group and also bind it with the dependable
52   *  registry.
53   *
54   *  For this layer, the {@link UpgradeService} interface <code>extends</code>
55   *  the {@link ExternalGMIListener} so as to be able to communicate with the
56   *  external upgrade manager.  This however, prevents us from having the 
57   *  {@link Layer} interface in the {@link UpgradeService} definition, since
58   *  this is a <code>Remote</code> interface, and thus needs to have its methods
59   *  declare a <code>RemoteException</code>.
60   *
61   *  @author Marcin Solarski
62   *  @author Hein Meling
63   *  @since Jgroup 2.1
64   */
65  public class UpgradeLayer
66    implements MembershipListener, UpgradeService
67  { 
68  
69    ////////////////////////////////////////////////////////////////////////////////////////////
70    // Logger
71    ////////////////////////////////////////////////////////////////////////////////////////////
72  
73    /** Obtain logger for this class */
74    private static final Logger log = Logger.getLogger(UpgradeLayer.class);
75  
76  
77    private static final int S_IDLE = 0;
78    private static final int S_IDLE_UPGRADING = 1;
79    private static final int S_UPGRADING = 2;
80    private static final int S_LEAVING = 3;
81    private static final int S_EXIT = 4;
82  
83    private static final String S_names[] = new String[] {
84      "S_IDLE",
85      "S_IDLE_UPGRADING",
86      "S_UPGRADING",
87      "S_LEAVING",
88      "S_EXIT"
89    };
90  
91    /** a flag enabling time measurements */
92    private static final boolean MEASUREMENT = true;
93  
94    /** array with times of state transitions */
95    private long times[] = new long [S_EXIT+1];
96  
97  
98    ////////////////////////////////////////////////////////////////////////////////////////////
99    // Fields
100   ////////////////////////////////////////////////////////////////////////////////////////////
101 
102   /** state of the UpgradeLayer */
103   private int state = S_IDLE;
104 
105   /** The upgrade listener associated with this GM layer. */
106   private UpgradeListener upgradeListener;
107 
108   private MemberId thisMember;
109 
110   /** the last view known to the upgrade layer */
111   private View currentView = new ViewImpl();
112   
113   /** the members of the view installed at the time the upgrade request is received */
114   private MemberId[] initialMembers = null;
115 
116   private Host localHost = DistributedSystemConfig.getLocalHost();
117 
118   private AppConfig thisApp;
119 
120   private MembershipService membershipService;
121 
122   private RegistryService registryService;
123 
124   /** a support variable that states whether this member is to be upgraded as next */
125   private boolean upgradable = false;
126 
127   /** a support variable that states whether this member can leave the group */
128   private boolean leavingTime = false;
129 
130 
131   ////////////////////////////////////////////////////////////////////////////////////////////
132   // Constructors
133   ////////////////////////////////////////////////////////////////////////////////////////////
134 
135   /**
136    *
137    */
138   private UpgradeLayer(MembershipService pgms, RegistryService regs, ExternalGMIService egmi)
139     throws JgroupException, RemoteException
140   {
141     membershipService = pgms;
142     thisMember = pgms.getMyIdentifier();
143     registryService = regs;
144     if (log.isDebugEnabled())
145       log.debug("UpgradeLayer constructed");
146   }
147 
148 
149   ////////////////////////////////////////////////////////////////////////////////////////////
150   // Static factory
151   ////////////////////////////////////////////////////////////////////////////////////////////
152 
153   public static UpgradeLayer getLayer(MembershipService pgms, RegistryService regs,
154       ExternalGMIService egmi) throws JgroupException, RemoteException
155   {
156     return new UpgradeLayer(pgms, regs, egmi);
157   }
158 
159 
160   ////////////////////////////////////////////////////////////////////////////////////////////
161   // Layer interface methods
162   ////////////////////////////////////////////////////////////////////////////////////////////
163 
164   /**
165    *  Add a listener object for upgraded events.
166    */
167   public void addListener(Object listener)
168   {
169     if (log.isDebugEnabled())
170       log.debug("Adding a listener of the UpgradeLayer");
171     if (listener == null)
172       throw new NullPointerException("No replica specified for addListener");
173     if (!(listener instanceof UpgradeListener))
174       throw new IllegalArgumentException("Specified listener do not implement the UpgradeListener interface");
175     if (upgradeListener != null)
176       throw new IllegalStateException("Only one upgrade listener is allowed for each replica.");
177     /* Get application object for this replica */
178     thisApp = AppConfig.getApplication(listener);
179     final String upgradeServiceName = UPGRADE_SERVICE + "-" + thisApp.getGroupId();
180 
181     try {
182       registryService.bind(upgradeServiceName, this);
183       if (log.isDebugEnabled())
184         log.debug("Bound the UpgradeLayer in the registry: " + upgradeServiceName);
185     } catch (Exception e) {
186       throw new IllegalStateException("Unable to bind " + upgradeServiceName + 
187           " in the dependable registry", e);
188     }
189 
190     /* Keep the server reference for invoking the upgraded method. */
191     upgradeListener = (UpgradeListener) listener;
192   }
193 
194 
195   ////////////////////////////////////////////////////////////////////////////////////////////
196   // UpgradeService interface methods
197   ////////////////////////////////////////////////////////////////////////////////////////////
198 
199   /**
200    *  The <code>upgradeRequest</code> method will initiate the upgrade
201    *  of the given application group to the new specified application
202    *  class.
203    *
204    *  This method is invoked through the EGMI layer, so the upgrade
205    *  manager must obtain a reference to this layer in order to request
206    *  an upgrade.
207    *
208    *  @exception UnknownGroupException
209    *    Raised if the specified application does not this replicas group.
210    */
211   @Multicast public synchronized void upgradeRequest(AppConfig newApp)
212     throws RemoteException, UnknownGroupException, ExecException
213   {
214     if (MEASUREMENT) {
215       times[S_IDLE_UPGRADING] = System.currentTimeMillis(); 
216       if (log.isDebugEnabled())
217         log.debug("S_IDLE_UPGRADING@" + thisMember + "time = " + times[S_IDLE_UPGRADING]);
218     }
219     if (log.isDebugEnabled())
220       log.debug("UpgradeRequest@UpgradeLayer: " + newApp);
221     if (state == S_UPGRADING) {
222       if (log.isDebugEnabled())
223         log.warn("Upgrade request ignored; already upgrading " + newApp);
224       return;
225     }
226 
227     // Change the layer state
228     state = S_IDLE_UPGRADING;
229     if (newApp.getGroupId() != currentView.getGid())
230       throw new UnknownGroupException("This replica has GroupId: ", currentView.getGid());
231     
232     // joan commment: The new and the old version of the server has to have the same
233     //                groupid. See applications.xml
234     
235     // Prepare the input data for the later selection algorithm
236     prepareSelection();
237 
238     // Start the upgrade thread that will perform the upgrade "in the background"
239     new Thread(new ReplicaUpgradingThread(this, newApp), "UPGRDADING_THREAD").start();
240   }
241 
242 
243   ////////////////////////////////////////////////////////////////////////////////////////////
244   // Private methods
245   ////////////////////////////////////////////////////////////////////////////////////////////
246 
247   private synchronized void wait_for_new_notifications()
248   {
249     try {
250       wait();
251     } catch (InterruptedException e) { /* Ignored; should never happen */ }
252     if (log.isDebugEnabled())
253       log.debug("After the UPGRADING_THREAD notification");
254   }
255 
256   /**
257    * prepares a later selection of the upgrade candidates.
258    * stores the highest localIDs for each EndPoint
259    * members with higher localIDs than the ones are considered
260    * new members in the future views during the upgrade process
261    */
262   private void prepareSelection()
263   {
264     initialMembers = currentView.getMembers();
265     if (log.isDebugEnabled())
266       log.debug("Initial view = " + currentView);
267     upgradable = amItheNext(currentView);
268   }
269 
270   /**
271    *  Checks whether this memeber is the one to be upgraded as 
272    *  the next one.
273    *  The current implementation is based on checking that
274    *  (1) this member has the smallest MemberId (which should 
275    *  be unique in the whole) and (2) it was in the view when 
276    *  the upgrade request was received. 
277    */
278   
279   /*  The implementation with 0-index 
280    *  in the view did not work because the new replicas may
281    *  get an index of 0. 
282    */
283   private boolean amItheNext(View view)
284   {
285     MemberId[] members = view.commonMembers(initialMembers);
286   
287     if (members.length == 0)
288       return false;
289     MemberId smallest = members[0];
290   
291     for (int i = 1; i < members.length; i++) {
292       if (smallest.compareTo(members[i]) > 0)
293         smallest = members[i];
294     }
295   
296     if (log.isDebugEnabled())
297       log.debug("amItheNext: Smallest member = " + smallest + " am I the next one = " + smallest.equals(thisMember));
298     if (smallest.equals(thisMember))
299       return true;
300     else
301       return false;
302   }
303 
304   /**
305    * check whether it is time for this replica to leave the group
306    * after a new replica is started. this implementation checks just 
307    * that the view size is greater than the view size before the upgrade
308    * and that there are at least 2 replicas running on the host local
309    * to the member being upgraded.
310    */
311   private boolean isItTimeToLeave(View view)
312   {
313     if (initialMembers == null)
314       return false;
315     int vl = view.size();
316     if (vl > initialMembers.length) {
317       int localReplicas = 0;
318       for (int i = 0; i < vl; i++) {
319         EndPointImpl ep = (EndPointImpl) view.getMembers()[i].getEndPoint();
320         if (ep.compareTo(thisMember.getEndPoint()) == 0)
321           localReplicas++;
322       }
323       if (log.isDebugEnabled())
324         log.debug("Number of replicas on the local host: " + localReplicas);
325       if (localReplicas > 1)
326         return true;
327     }
328     return false;
329   
330   }
331 
332   /**
333    * prints the time of state transitions of the upgrade algorithm
334    */
335   private void showTimes() {
336     log.info("State transition times for replica at " + thisMember.getEndPoint().getIntAddress());
337     StringBuilder b = new StringBuilder();
338     for(int i=S_IDLE_UPGRADING; i<times.length-1; i++)
339       b.append(S_names[i] + "\t");
340     log.info(b);
341     
342     b = new StringBuilder();
343     for(int i=S_IDLE_UPGRADING; i<times.length-1; i++)
344       b.append(times[i] + "\t");
345     log.info(b);
346     log.info(" Time Diffs:");
347     b = new StringBuilder();
348     for(int i=S_IDLE_UPGRADING; i<times.length-1; i++)
349       b.append((times[i]- times[i-1]) + "\t");
350     log.info(b);
351   }
352 
353   private boolean upgradeEnabled(AppConfig newApp)
354   {
355     int vs = currentView.size();
356     if (log.isDebugEnabled())
357       log.debug("Checking the upgrade enabling condition: View.size= "  + vs +  ", upgradable: " + upgradable);
358     return (vs > newApp.getMinimalRedundancy() && upgradable);
359     //joan comment: Make Hein explain the line above.
360   }
361 
362 
363   ////////////////////////////////////////////////////////////////////////////////////////////
364   // Methods from MembershipListener
365   ////////////////////////////////////////////////////////////////////////////////////////////
366   
367   /**
368    *  Upcall that is invoked by Jgroup when a view change occur in this
369    *  replica's object group.
370    *
371    *  @param view
372    *    The new view object group.
373    */
374   public synchronized void viewChange(View view)
375   {
376     if (log.isDebugEnabled())
377       log.debug("UpgradeLayer:viewChange: " + view);
378     /* Keep the current view for later access */
379     currentView = view;
380   
381     upgradable = amItheNext(view);
382     if (log.isDebugEnabled())
383       log.debug("Checking if I'm the replica to be upgraded next: " + upgradable);
384   
385     /* check whether it is time for this replica to leave the group */
386     leavingTime = isItTimeToLeave(view);
387   
388     notifyAll();
389   }
390 
391   /**
392    *  Upcall that is invoked by Jgroup to acknowledge the fact that an
393    *  object is not member of a group any more more:
394    *
395    */
396   public void hasLeft()
397   {
398     if (log.isDebugEnabled()) log.debug("Replica upgraded");
399     upgradeListener.upgraded();
400     boolean removed = localHost.removeReplica(thisApp);
401   }
402 
403   /**
404    *  Currently not implemented.
405    *
406    */
407   public void prepareChange()
408   {
409   }
410 
411 
412   ////////////////////////////////////////////////////////////////////////////////////////////
413   // Private inner class
414   ////////////////////////////////////////////////////////////////////////////////////////////
415 
416   /**
417    * an internal class implementing most of the upgrade algorithm.
418    * It is started in another thread than the invocation thread of the 
419    * upgrade request to carry out the algorithm asynchronously to
420    * the upgrade request (the decision taken to overcome the bug
421    * of the local membership service of Jgroup2)
422    */
423   private class ReplicaUpgradingThread
424     implements Runnable
425   {
426     private AppConfig newApp;
427     private UpgradeLayer monitor;
428 
429     ReplicaUpgradingThread(UpgradeLayer monitor, AppConfig newApp)
430     {
431       this.monitor=monitor;
432       this.newApp=newApp; 
433     }
434 
435     public void run()
436     {
437       if (log.isDebugEnabled())
438         log.debug("UpgradeLayer: Entering the loop checking the enabling condition upgrading of this replica");
439       while (!upgradeEnabled(newApp)) {
440         if (log.isDebugEnabled())
441           log.debug("UpgradeLayer: in the loop after having checked the condition");
442         wait_for_new_notifications();
443       }
444       
445       //joan comment: Is this where this UpgradeServer has become part of the set R
446       //              described in the algorithm, and has not yet been picked for 
447       //              upgrade ? 
448       
449       if (log.isDebugEnabled())
450         log.debug("UpgradeLayer: Leaving the loop checking the enabling condition upgrading of this replica");
451 
452       /* FIXME Here we need to block requests to this replica, or ? */
453 
454       state = S_UPGRADING;
455       if (MEASUREMENT) {
456         times[S_UPGRADING] = System.currentTimeMillis(); 
457         if (log.isDebugEnabled())
458           log.debug("S_UPGRADING@" + thisMember + "time = " + times[S_UPGRADING]);
459       }
460 
461       try {
462         localHost.createReplica(newApp);
463         //joan comment: Ask Hein if this is the ExecDeamon.
464         
465       } catch (Exception ex) {
466         log.error("Cannot start a new replica", ex);
467       }
468       if (log.isDebugEnabled())
469         log.debug("After a replica is triggered to be created");
470 
471       // Waiting for the new replica to join the group
472       while (!leavingTime) {
473         if (log.isDebugEnabled())
474           log.debug("UpgradeLayer: S_UPGRADING loop");
475         wait_for_new_notifications();
476       }
477 
478       state = S_LEAVING;
479       if (MEASUREMENT) {
480         times[S_LEAVING] = System.currentTimeMillis(); 
481         if (log.isDebugEnabled())
482           log.debug("S_LEAVING@" + thisMember + "time = " + times[S_LEAVING]);
483       }
484       showTimes();
485       if (log.isDebugEnabled())
486         log.debug("Releasing the replica ...");
487       try {
488         membershipService.leave();
489         registryService.unbind();
490         if (log.isDebugEnabled())
491           log.debug("Replica successfully released.");
492         state = S_EXIT;
493         if (MEASUREMENT) {
494           times[S_EXIT] = System.currentTimeMillis(); 
495           if (log.isDebugEnabled())
496             log.debug("S_EXIT@" + thisMember + "time = " + times[S_EXIT]);
497         }
498         // we should not exit the jvm since it would kill the ExecDaemon
499         //System.exit(0);
500       } catch (JgroupException e) {
501         log.error("An exception while releasing a replica", e);
502       } catch (RemoteException e) {
503         log.error("An exception while unbinding replica", e);
504       } catch (NotBoundException e) {
505         log.error("An exception while unbinding replica", e);
506       }
507     }
508   }
509 
510 
511 } // END UpgradeLayer