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.relacs.config;
20  
21  import static jgroup.util.log.ReplicaEvent.Type.Created;
22  
23  import java.io.Serializable;
24  import java.lang.reflect.UndeclaredThrowableException;
25  import java.net.InetAddress;
26  import java.net.UnknownHostException;
27  import java.rmi.AccessException;
28  import java.rmi.NotBoundException;
29  import java.rmi.Remote;
30  import java.rmi.RemoteException;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import jgroup.core.arm.ExecException;
39  import jgroup.core.arm.ExecService;
40  import jgroup.core.registry.BootstrapRegistry;
41  import jgroup.relacs.types.EndPointImpl;
42  import jgroup.util.log.Eventlogger;
43  import jgroup.util.log.ReplicaEvent;
44  
45  import org.apache.log4j.Logger;
46  
47  /**
48   *  The <code>Host</code> class contains information about a single host
49   *  in a domain.
50   *
51   *  @author Alberto Montresor
52   *  @author Hein Meling
53   *  @since Jgroup 1.2
54   */
55  public class Host
56    extends EndPointImpl
57    implements Iterable<AppConfig>
58  {
59  
60    ////////////////////////////////////////////////////////////////////////////////////////////
61    // Logger
62    ////////////////////////////////////////////////////////////////////////////////////////////
63  
64    /** Obtain logger for this class */
65    private static final Logger log = Logger.getLogger(Host.class);
66  
67  
68    ////////////////////////////////////////////////////////////////////////////////////////////
69    // Constants
70    ////////////////////////////////////////////////////////////////////////////////////////////
71  
72    private static final long serialVersionUID = 5712317513876955415L;
73  
74    /**
75     *  Key for the <code>Host</code> content map; to obtain a remote
76     *  reference for the execution daemon running on the associated host.
77     */
78    private static final String EXEC_DAEMON = "ExecDaemon";
79  
80    /**
81     * Replica state constants
82     */
83    public enum ReplicaState { FAILED_CREATION, JOIN_PENDING, NORMAL, REMOVE_PENDING }
84  
85  
86    ////////////////////////////////////////////////////////////////////////////////////////////
87    // Fields
88    ////////////////////////////////////////////////////////////////////////////////////////////
89  
90    /** Host name */
91    private String hostName;
92  
93    /** Domain of this <code>Host</code> object */
94    private Domain domain;
95  
96    /** Fully Qualified Domain Name (FQDN) */
97    private String fqdn;
98  
99    /** Map for storing content associated with this <code>Host</code> object */
100   private final Map<String,Object> content = new HashMap<String,Object>();
101 
102   /** Set of replicas assigned to this host */
103   private final Set<AppConfig> replicas = Collections.synchronizedSet(new HashSet<AppConfig>());
104 
105   /** Map for keeping track of the replica state of the applications on this host */
106   private final Map<AppConfig,ReplicaState> appReplicaState = new HashMap<AppConfig,ReplicaState>();
107 
108 
109   ////////////////////////////////////////////////////////////////////////////////////////////
110   // Constructors
111   ////////////////////////////////////////////////////////////////////////////////////////////
112 
113   /**
114    *  Default constructor for externalization
115    */
116   public Host()
117   {
118     // Ensure that the fqdn is set after deserialization
119     fqdn = getAddress().getCanonicalHostName();
120   }
121 
122 
123   /**
124    *  Construct a new <code>Host</code> object containing information
125    *  about the domain, the host and the port number for an entry in the
126    *  <code>HostSet</code>.
127    */
128   public Host(String hostName, Domain domain, int port)
129     throws UnknownHostException
130   {
131     this(getFQDN(hostName, domain), port);
132     this.hostName = hostName;
133     this.domain = domain;
134     /*
135      * If the <code>EndPointImpl</code> (see private constructor below)
136      * sets this <code>Host</code> as the local host, we must also set
137      * the provided <code>Domain</code> to be the local domain.
138      */
139     if (local)
140       domain.setLocal(true);
141   }
142 
143 
144   /**
145    *  Private constructor for creating a new <code>Host</code> instance,
146    *  and initializing the <code>EndPointImpl</code> superclass.
147    */
148   private Host(String fqdn, int port)
149     throws UnknownHostException
150   {
151     super(InetAddress.getByName(fqdn), port);
152     this.fqdn = fqdn;
153   }
154 
155 
156   /**
157    *  Obtain a stringified fully qualified domain name for the given
158    *  host and domain.  That is simply a concatenation of the hostname
159    *  and the domain name with a dot between.
160    */
161   private static String getFQDN(String hostName, Domain domain)
162   {
163     String fqdn = hostName;
164     String domainName = domain.getName();
165     if (!(fqdn.indexOf(".") > -1) && !domainName.equals(""))
166       fqdn += "." + domainName;
167     return fqdn;
168   }
169 
170 
171   ////////////////////////////////////////////////////////////////////////////////////////////
172   // Host interface methods
173   ////////////////////////////////////////////////////////////////////////////////////////////
174 
175   /**
176    *  Returns the fully qualified domain name (host name and domain
177    *  name) for this <code>Host</code> object.
178    */
179   public String getCanonicalHostName()
180   {
181     return fqdn;
182   }
183 
184 
185   /**
186    *  Returns the host name for this <code>Host</code> object,
187    *  excluding the domain name component.
188    */
189   public String getHostName()
190   {
191     return hostName;
192   }
193 
194 
195   /**
196    *  Returns the domain name for this <code>Host</code> object,
197    *  excluding the host name component.
198    */
199   public String getDomainName()
200   {
201     return domain.getName();
202   }
203 
204 
205   /**
206    *  Returns the <code>Domain</code> object associated with
207    *  this <code>Host</code> object.
208    */
209   public Domain getDomain()
210   {
211     return domain;
212   }
213 
214   ////////////////////////////////////////////////////////////////////////////////////////////
215   // Replica assignment handling
216   ////////////////////////////////////////////////////////////////////////////////////////////
217 
218   public Iterator<AppConfig> iterator()
219   {
220     return replicas.iterator();
221   }
222 
223   public boolean contains(AppConfig app)
224   {
225     return replicas.contains(app);
226   }
227 
228   public int replicaCount()
229   {
230     return replicas.size();
231   }
232 
233   /**
234    * Assign the <code>app</code> replica to this host.
235    * 
236    * @param app
237    */
238   public void assign(AppConfig app)
239   {
240     replicas.add(app);
241   }
242 
243   public void remove(AppConfig app)
244   {
245     replicas.remove(app);
246   }
247 
248 
249   ////////////////////////////////////////////////////////////////////////////////////////////
250   // Content map
251   ////////////////////////////////////////////////////////////////////////////////////////////
252 
253   /**
254    *  Retrieve the value associated with this host for the given key.
255    */
256   public Object get(String key)
257   {
258     return content.get(key);
259   }
260 
261 
262   /**
263    *  Associate the given value with this host under the given key.
264    */
265   public Object put(String key, Object value)
266   {
267     return content.put(key, value);
268   }
269 
270   public Map<String,Object> getContentMap()
271   {
272     Map<String,Object> xferContent = new HashMap<String,Object>(content);
273     for (Iterator iter = xferContent.values().iterator(); iter.hasNext();) {
274       Object obj = (Object) iter.next();
275       if (!(obj instanceof Serializable))
276         iter.remove();
277     }
278     return xferContent;
279   }
280 
281   public void setContentMap(Map<String,Object> c)
282   {
283     this.content.putAll(c);
284   }
285 
286   ////////////////////////////////////////////////////////////////////////////////////////////
287   // Host availability
288   ////////////////////////////////////////////////////////////////////////////////////////////
289 
290   /**
291    * Returns true if the host is available; otherwise false.
292    * The method will also update the reachability status of the host.
293    */
294   public boolean isAvailable()
295   {
296     ExecService execRef = getExecDaemon();
297     content.put(EXEC_DAEMON, execRef);
298     return (execRef != null);
299   }
300 
301   /**
302    * Convenience method to update this hosts reachability status.
303    */
304   public void suspect()
305   {
306     isAvailable();
307   }
308 
309   /**
310    *  Returns true if this host responded to a ping invocation of its
311    *  <code>ExecService</code>.  False is returned otherwise.
312    */
313   public boolean ping()
314   {
315     if (isAvailable()) {
316       ExecService execRef = getExecRef();
317       try {
318         execRef.ping();
319         return true;
320       } catch (RemoteException e) {
321         log.warn("ping failed: " + execRef, e);
322       }
323     }
324     return false;
325   }
326 
327   public Remote lookup(String name)
328     throws AccessException, UnknownHostException, RemoteException, NotBoundException
329   {
330     return BootstrapRegistry.lookup(fqdn, name);
331   }
332 
333   /**
334    *  Get the execution daemon remote reference for the specified host.
335    *  If the reference is not stored in the host object or was
336    *  previously unavailable, we try to update it by looking up the
337    *  bootstrap registry running on the specified host.
338    *
339    *  @return 
340    *    The <code>ExecService</code> interface reference to the remote
341    *    object of the execution daemon running on the specified host.
342    *    If the return value is <code>null</code>, the specified host
343    *    should be considered unavailable.
344    */
345   private ExecService getExecRef()
346   {
347     ExecService execRef = (ExecService) content.get(EXEC_DAEMON);
348     if (execRef == null) {
349       execRef = getExecDaemon();
350       content.put(EXEC_DAEMON, execRef);
351     }
352     return execRef;
353   }
354 
355 
356   /**
357    *  Get the ExecDaemon reference for this host, by looking up
358    *  in the bootstrap registry running on this host.
359    *
360    *  @return 
361    *    The <code>ExecService</code> interface reference to the remote
362    *    object of the execution daemon running on this host.
363    *    If the return value is <code>null</code>, this host
364    *    should be considered unavailable.
365    */
366   private ExecService getExecDaemon()
367   {
368 //    if (log.isDebugEnabled())
369 //      log.debug("Host " + fqdn + (isLocal() ? " is local" : " is remote"));
370     try {
371       return (ExecService) lookup(ExecService.EXEC_DAEMON_NAME);
372 
373     } catch (RemoteException exception) {
374       if (log.isDebugEnabled())
375         log.debug("Host " + fqdn + " is unavailable");
376       return null;
377     } catch (NotBoundException exception) {
378       if (log.isDebugEnabled())
379         log.debug("The ExecDaemon is not running on host " + fqdn);
380       return null;
381     } catch (UnknownHostException e) {
382       if (log.isDebugEnabled())
383         log.debug("Host " + fqdn + " is unknown", e);
384       return null;
385     }
386   }
387 
388 
389   ////////////////////////////////////////////////////////////////////////////////////////////
390   // Replica management
391   ////////////////////////////////////////////////////////////////////////////////////////////
392 
393   /**
394    *  Create a replica for the given <code>app</code> on this host.
395    *
396    *  @param app
397    *    The <code>app</code> for which to create a replica.
398    *  @return 
399    *    True if the creation was executed without any problems; false otherwise.
400    */
401   public boolean createReplica(AppConfig app)
402   {
403     ExecService exec = getExecRef();
404     if (exec == null) {
405       log.warn("Execution service unavailable on " + this);
406       appReplicaState.put(app, ReplicaState.FAILED_CREATION);
407       return false;
408     }
409 
410     ClassData cdata = app.getClassData();
411     boolean created = false;
412     try {
413       if (app.startInExecJVM()) {
414         created = exec.createExecReplica(cdata);
415       } else {
416         created = exec.createReplica(cdata);
417       }
418       if (created) {
419         appReplicaState.put(app, ReplicaState.JOIN_PENDING);
420         log.info(cdata.getShortName() +" replica successfully created on " + this);
421         if (Eventlogger.ENABLED)
422           Eventlogger.logEventFlush(new ReplicaEvent(Created, app.getGroupId(), this.address));
423       } else {
424         ReplicaState state = getState(app);
425         log.info(cdata.getShortName() +" replica already created on " + this + ", state: " + state);
426         // Since it was already created we should return true from this method
427         created = true;
428       }
429     } catch (UndeclaredThrowableException e) {
430       log.warn("Failed to create replica "+ cdata.getShortName() +" on: "+ this, e);
431     } catch (RemoteException re) {
432       log.warn("Failed to create replica "+ cdata.getShortName() +" on: "+ this, re);
433     } catch (ExecException ex) {
434       if (log.isDebugEnabled()) {
435         log.debug(ex.getMessage());
436         Throwable e = ex.getCause();
437         if (e != null)
438           log.warn(e.getMessage());
439       }
440     }
441     if (!created) {
442       appReplicaState.put(app, ReplicaState.FAILED_CREATION);
443     }
444     return created;
445   }
446 
447 
448   /**
449    *  Remove the replica for <code>app</code> running on <code>host</code>.
450    *
451    *  @param app
452    *    The <code>app</code> whose replica to remove.
453    *  @param host
454    *    The <code>host</code> from which to remove the given replica.
455    *  @return 
456    *    True if the removal was executed without any problems; false otherwise.
457    */
458   public boolean removeReplica(AppConfig app)
459   {
460 //    if (log.isDebugEnabled())
461 //      log.debug("isLocal: " + isLocal());
462     ExecService exec = getExecRef();
463     if (exec == null) {
464       if (log.isDebugEnabled())
465         log.debug("Execution service unavailable on " + this);
466       return false;
467     }
468 
469     ClassData cdata = app.getClassData();
470     try {
471       boolean removed = exec.removeReplica(cdata);
472       if (log.isDebugEnabled()) {
473         if (removed) {
474           log.info(cdata.getShortName() +" replica successfully removed on " + this);
475         } else {
476           log.info(cdata.getShortName() +" replica already removed on " + this);
477         }
478       }
479     } catch (UndeclaredThrowableException e) {
480       log.warn("Failed to remove replica "+ cdata.getShortName() +" on: "+ this, e);
481       return false;
482     } catch (RemoteException re) {
483       log.warn("Failed to remove replica "+ cdata.getShortName() +" on: "+ this, re);
484       return false;
485     }
486     // Update the ReplicaState with the newly removed application
487     //FIXME this should be updated also when we return false
488     appReplicaState.put(app, ReplicaState.REMOVE_PENDING);
489     return true;
490   }
491 
492 
493   /**
494    * Return the set of replicas running on this host as reported by the ExecDaemon.
495    */
496   public Set<ClassData> queryReplicas()
497   {
498     ExecService exec = getExecRef();
499     if (exec == null) {
500       if (log.isDebugEnabled())
501         log.debug("Execution service unavailable on " + this);
502       return new HashSet<ClassData>(0);
503     }
504     try {
505       return exec.queryReplicas();
506     } catch (RemoteException e) {
507       log.warn("Could not access the execution service on " + this, e);
508       return new HashSet<ClassData>(0);
509     }
510   }
511 
512   /**
513    * Returns true if the given application is currently joining at this host.
514    */
515   public boolean isJoining(AppConfig app)
516   {
517     ReplicaState state = appReplicaState.get(app);
518     if (state == null)
519       return false;
520     else
521       return state.equals(ReplicaState.JOIN_PENDING);
522   }
523 
524   /**
525    * Returns the current replica state of the given application on this host.
526    */
527   public ReplicaState getState(AppConfig app)
528   {
529     return appReplicaState.get(app);
530   }
531 
532   /**
533    * Mark the given application as in the normal state.
534    */
535   public void viewChange(AppConfig app)
536   {
537     appReplicaState.put(app, ReplicaState.NORMAL);
538   }
539 
540   /**
541    * Request the execution service on this host to shutdown.
542    */
543   public void shutdown(int delay)
544   {
545     ExecService exec = getExecDaemon();
546     if (exec == null) {
547       if (log.isDebugEnabled())
548         log.debug("Execution service unavailable on " + this);
549       return;
550     }
551     try {
552       exec.shutdown(delay);
553     } catch (RemoteException e) {
554       log.warn("Could not access the execution service on " + this, e);
555     }
556   }
557 
558 } // END Host