View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.replication.regionserver;
21  
22  import com.google.common.annotations.VisibleForTesting;
23  import com.google.common.util.concurrent.ThreadFactoryBuilder;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Random;
34  import java.util.Set;
35  import java.util.SortedSet;
36  import java.util.TreeSet;
37  import java.util.UUID;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.concurrent.CopyOnWriteArrayList;
40  import java.util.concurrent.LinkedBlockingQueue;
41  import java.util.concurrent.RejectedExecutionException;
42  import java.util.concurrent.ThreadPoolExecutor;
43  import java.util.concurrent.TimeUnit;
44  
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.apache.hadoop.conf.Configuration;
48  import org.apache.hadoop.fs.FileSystem;
49  import org.apache.hadoop.fs.Path;
50  import org.apache.hadoop.hbase.CompatibilitySingletonFactory;
51  import org.apache.hadoop.hbase.HConstants;
52  import org.apache.hadoop.hbase.Server;
53  import org.apache.hadoop.hbase.TableDescriptors;
54  import org.apache.hadoop.hbase.TableName;
55  import org.apache.hadoop.hbase.classification.InterfaceAudience;
56  import org.apache.hadoop.hbase.regionserver.HRegionServer;
57  import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
58  import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
59  import org.apache.hadoop.hbase.replication.ReplicationException;
60  import org.apache.hadoop.hbase.replication.ReplicationListener;
61  import org.apache.hadoop.hbase.replication.ReplicationPeer;
62  import org.apache.hadoop.hbase.replication.ReplicationPeer.PeerState;
63  import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
64  import org.apache.hadoop.hbase.replication.ReplicationPeers;
65  import org.apache.hadoop.hbase.replication.ReplicationQueueInfo;
66  import org.apache.hadoop.hbase.replication.ReplicationQueues;
67  import org.apache.hadoop.hbase.replication.ReplicationTracker;
68  import org.apache.hadoop.hbase.util.Pair;
69  import org.apache.hadoop.hbase.wal.DefaultWALProvider;
70  
71  /**
72   * This class is responsible to manage all the replication
73   * sources. There are two classes of sources:
74   * <ul>
75   * <li> Normal sources are persistent and one per peer cluster</li>
76   * <li> Old sources are recovered from a failed region server and our
77   * only goal is to finish replicating the WAL queue it had up in ZK</li>
78   * </ul>
79   *
80   * When a region server dies, this class uses a watcher to get notified and it
81   * tries to grab a lock in order to transfer all the queues in a local
82   * old source.
83   *
84   * This class implements the ReplicationListener interface so that it can track changes in
85   * replication state.
86   */
87  @InterfaceAudience.Private
88  public class ReplicationSourceManager implements ReplicationListener {
89    private static final Log LOG =
90        LogFactory.getLog(ReplicationSourceManager.class);
91    // List of all the sources that read this RS's logs
92    private final List<ReplicationSourceInterface> sources;
93    // List of all the sources we got from died RSs
94    private final List<ReplicationSourceInterface> oldsources;
95    private final ReplicationQueues replicationQueues;
96    private final ReplicationTracker replicationTracker;
97    private final ReplicationPeers replicationPeers;
98    // UUID for this cluster
99    private final UUID clusterId;
100   // All about stopping
101   private final Server server;
102   // All logs we are currently tracking
103   // Index structure of the map is: peer_id->logPrefix/logGroup->logs
104   private final Map<String, Map<String, SortedSet<String>>> walsById;
105   // Logs for recovered sources we are currently tracking
106   private final Map<String, Map<String, SortedSet<String>>> walsByIdRecoveredQueues;
107   private final Configuration conf;
108   private final FileSystem fs;
109   // The paths to the latest log of each wal group, for new coming peers
110   private Set<Path> latestPaths;
111   // Path to the wals directories
112   private final Path logDir;
113   // Path to the wal archive
114   private final Path oldLogDir;
115   // The number of ms that we wait before moving znodes, HBASE-3596
116   private final long sleepBeforeFailover;
117   // Homemade executer service for replication
118   private final ThreadPoolExecutor executor;
119 
120   private final Random rand;
121   private final boolean replicationForBulkLoadDataEnabled;
122 
123 
124   /**
125    * Creates a replication manager and sets the watch on all the other registered region servers
126    * @param replicationQueues the interface for manipulating replication queues
127    * @param replicationPeers
128    * @param replicationTracker
129    * @param conf the configuration to use
130    * @param server the server for this region server
131    * @param fs the file system to use
132    * @param logDir the directory that contains all wal directories of live RSs
133    * @param oldLogDir the directory where old logs are archived
134    * @param clusterId
135    */
136   public ReplicationSourceManager(final ReplicationQueues replicationQueues,
137       final ReplicationPeers replicationPeers, final ReplicationTracker replicationTracker,
138       final Configuration conf, final Server server, final FileSystem fs, final Path logDir,
139       final Path oldLogDir, final UUID clusterId) {
140     //CopyOnWriteArrayList is thread-safe.
141     //Generally, reading is more than modifying.
142     this.sources = new CopyOnWriteArrayList<ReplicationSourceInterface>();
143     this.replicationQueues = replicationQueues;
144     this.replicationPeers = replicationPeers;
145     this.replicationTracker = replicationTracker;
146     this.server = server;
147     this.walsById = new HashMap<String, Map<String, SortedSet<String>>>();
148     this.walsByIdRecoveredQueues = new ConcurrentHashMap<String, Map<String, SortedSet<String>>>();
149     this.oldsources = new CopyOnWriteArrayList<ReplicationSourceInterface>();
150     this.conf = conf;
151     this.fs = fs;
152     this.logDir = logDir;
153     this.oldLogDir = oldLogDir;
154     this.sleepBeforeFailover =
155         conf.getLong("replication.sleep.before.failover", 30000); // 30 seconds
156     this.clusterId = clusterId;
157     this.replicationTracker.registerListener(this);
158     this.replicationPeers.getAllPeerIds();
159     // It's preferable to failover 1 RS at a time, but with good zk servers
160     // more could be processed at the same time.
161     int nbWorkers = conf.getInt("replication.executor.workers", 1);
162     // use a short 100ms sleep since this could be done inline with a RS startup
163     // even if we fail, other region servers can take care of it
164     this.executor = new ThreadPoolExecutor(nbWorkers, nbWorkers,
165         100, TimeUnit.MILLISECONDS,
166         new LinkedBlockingQueue<Runnable>());
167     ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
168     tfb.setNameFormat("ReplicationExecutor-%d");
169     tfb.setDaemon(true);
170     this.executor.setThreadFactory(tfb.build());
171     this.rand = new Random();
172     this.latestPaths = Collections.synchronizedSet(new HashSet<Path>());
173     replicationForBulkLoadDataEnabled =
174         conf.getBoolean(HConstants.REPLICATION_BULKLOAD_ENABLE_KEY,
175           HConstants.REPLICATION_BULKLOAD_ENABLE_DEFAULT);
176   }
177 
178   /**
179    * Provide the id of the peer and a log key and this method will figure which
180    * wal it belongs to and will log, for this region server, the current
181    * position. It will also clean old logs from the queue.
182    * @param log Path to the log currently being replicated from
183    * replication status in zookeeper. It will also delete older entries.
184    * @param id id of the peer cluster
185    * @param position current location in the log
186    * @param queueRecovered indicates if this queue comes from another region server
187    * @param holdLogInZK if true then the log is retained in ZK
188    */
189   public void logPositionAndCleanOldLogs(Path log, String id, long position,
190       boolean queueRecovered, boolean holdLogInZK) {
191     String fileName = log.getName();
192     this.replicationQueues.setLogPosition(id, fileName, position);
193     if (holdLogInZK) {
194      return;
195     }
196     cleanOldLogs(fileName, id, queueRecovered);
197   }
198 
199   /**
200    * Cleans a log file and all older files from ZK. Called when we are sure that a
201    * log file is closed and has no more entries.
202    * @param key Path to the log
203    * @param id id of the peer cluster
204    * @param queueRecovered Whether this is a recovered queue
205    */
206   public void cleanOldLogs(String key, String id, boolean queueRecovered) {
207     String logPrefix = DefaultWALProvider.getWALPrefixFromWALName(key);
208     if (queueRecovered) {
209       SortedSet<String> wals = walsByIdRecoveredQueues.get(id).get(logPrefix);
210       if (wals != null && !wals.first().equals(key)) {
211         cleanOldLogs(wals, key, id);
212       }
213     } else {
214       synchronized (this.walsById) {
215         SortedSet<String> wals = walsById.get(id).get(logPrefix);
216         if (wals != null && !wals.first().equals(key)) {
217           cleanOldLogs(wals, key, id);
218         }
219       }
220     }
221  }
222 
223   private void cleanOldLogs(SortedSet<String> wals, String key, String id) {
224     SortedSet<String> walSet = wals.headSet(key);
225     LOG.debug("Removing " + walSet.size() + " logs in the list: " + walSet);
226     for (String wal : walSet) {
227       this.replicationQueues.removeLog(id, wal);
228     }
229     walSet.clear();
230   }
231 
232   /**
233    * Adds a normal source per registered peer cluster and tries to process all
234    * old region server wal queues
235    */
236   protected void init() throws IOException, ReplicationException {
237     for (String id : this.replicationPeers.getPeerIds()) {
238       addSource(id);
239       if (replicationForBulkLoadDataEnabled) {
240         // Check if peer exists in hfile-refs queue, if not add it. This can happen in the case
241         // when a peer was added before replication for bulk loaded data was enabled.
242         this.replicationQueues.addPeerToHFileRefs(id);
243       }
244     }
245     List<String> currentReplicators = this.replicationQueues.getListOfReplicators();
246     if (currentReplicators == null || currentReplicators.isEmpty()) {
247       return;
248     }
249     List<String> otherRegionServers = replicationTracker.getListOfRegionServers();
250     LOG.info("Current list of replicators: " + currentReplicators + " other RSs: "
251         + otherRegionServers);
252 
253     // Look if there's anything to process after a restart
254     for (String rs : currentReplicators) {
255       if (!otherRegionServers.contains(rs)) {
256         transferQueues(rs);
257       }
258     }
259   }
260 
261   /**
262    * Add sources for the given peer cluster on this region server. For the newly added peer, we only
263    * need to enqueue the latest log of each wal group and do replication
264    * @param id the id of the peer cluster
265    * @return the source that was created
266    * @throws IOException
267    */
268   protected ReplicationSourceInterface addSource(String id) throws IOException,
269       ReplicationException {
270     ReplicationPeerConfig peerConfig = replicationPeers.getReplicationPeerConfig(id);
271     ReplicationPeer peer = replicationPeers.getPeer(id);
272     ReplicationSourceInterface src =
273         getReplicationSource(this.conf, this.fs, this, this.replicationQueues,
274           this.replicationPeers, server, id, this.clusterId, peerConfig, peer);
275     synchronized (this.walsById) {
276       this.sources.add(src);
277       Map<String, SortedSet<String>> walsByGroup = new HashMap<String, SortedSet<String>>();
278       this.walsById.put(id, walsByGroup);
279       // Add the latest wal to that source's queue
280       synchronized (latestPaths) {
281         if (this.latestPaths.size() > 0) {
282           for (Path logPath : latestPaths) {
283             String name = logPath.getName();
284             String walPrefix = DefaultWALProvider.getWALPrefixFromWALName(name);
285             SortedSet<String> logs = new TreeSet<String>();
286             logs.add(name);
287             walsByGroup.put(walPrefix, logs);
288             try {
289               this.replicationQueues.addLog(id, name);
290             } catch (ReplicationException e) {
291               String message =
292                   "Cannot add log to queue when creating a new source, queueId=" + id
293                       + ", filename=" + name;
294               server.stop(message);
295               throw e;
296             }
297             src.enqueueLog(logPath);
298           }
299         }
300       }
301     }
302     src.startup();
303     return src;
304   }
305 
306   /**
307    * Delete a complete queue of wals associated with a peer cluster
308    * @param peerId Id of the peer cluster queue of wals to delete
309    */
310   public void deleteSource(String peerId, boolean closeConnection) {
311     this.replicationQueues.removeQueue(peerId);
312     if (closeConnection) {
313       this.replicationPeers.peerRemoved(peerId);
314     }
315   }
316 
317   /**
318    * Terminate the replication on this region server
319    */
320   public void join() {
321     this.executor.shutdown();
322     if (this.sources.size() == 0) {
323       this.replicationQueues.removeAllQueues();
324     }
325     for (ReplicationSourceInterface source : this.sources) {
326       source.terminate("Region server is closing");
327     }
328   }
329 
330   /**
331    * Get a copy of the wals of the first source on this rs
332    * @return a sorted set of wal names
333    */
334   protected Map<String, Map<String, SortedSet<String>>> getWALs() {
335     return Collections.unmodifiableMap(walsById);
336   }
337 
338   /**
339    * Get a copy of the wals of the recovered sources on this rs
340    * @return a sorted set of wal names
341    */
342   protected Map<String, Map<String, SortedSet<String>>> getWalsByIdRecoveredQueues() {
343     return Collections.unmodifiableMap(walsByIdRecoveredQueues);
344   }
345 
346   /**
347    * Get a list of all the normal sources of this rs
348    * @return lis of all sources
349    */
350   public List<ReplicationSourceInterface> getSources() {
351     return this.sources;
352   }
353 
354   /**
355    * Get a list of all the old sources of this rs
356    * @return list of all old sources
357    */
358   public List<ReplicationSourceInterface> getOldSources() {
359     return this.oldsources;
360   }
361 
362   /**
363    * Get the normal source for a given peer
364    * @param peerId
365    * @return the normal source for the give peer if it exists, otherwise null.
366    */
367   public ReplicationSourceInterface getSource(String peerId) {
368     for (ReplicationSourceInterface source: getSources()) {
369       if (source.getPeerClusterId().equals(peerId)) {
370         return source;
371       }
372     }
373     return null;
374   }
375 
376   @VisibleForTesting
377   List<String> getAllQueues() {
378     return replicationQueues.getAllQueues();
379   }
380 
381   void preLogRoll(Path newLog) throws IOException {
382     recordLog(newLog);
383     String logName = newLog.getName();
384     String logPrefix = DefaultWALProvider.getWALPrefixFromWALName(logName);
385     synchronized (latestPaths) {
386       Iterator<Path> iterator = latestPaths.iterator();
387       while (iterator.hasNext()) {
388         Path path = iterator.next();
389         if (path.getName().contains(logPrefix)) {
390           iterator.remove();
391           break;
392         }
393       }
394       this.latestPaths.add(newLog);
395     }
396   }
397 
398   /**
399    * Check and enqueue the given log to the correct source. If there's still no source for the
400    * group to which the given log belongs, create one
401    * @param logPath the log path to check and enqueue
402    * @throws IOException
403    */
404   private void recordLog(Path logPath) throws IOException {
405     String logName = logPath.getName();
406     String logPrefix = DefaultWALProvider.getWALPrefixFromWALName(logName);
407     // update replication queues on ZK
408     synchronized (replicationPeers) {// synchronize on replicationPeers to avoid adding source for
409                                      // the to-be-removed peer
410       for (String id : replicationPeers.getPeerIds()) {
411         try {
412           this.replicationQueues.addLog(id, logName);
413         } catch (ReplicationException e) {
414           throw new IOException("Cannot add log to replication queue"
415               + " when creating a new source, queueId=" + id + ", filename=" + logName, e);
416         }
417       }
418     }
419     // update walsById map
420     synchronized (walsById) {
421       for (Map.Entry<String, Map<String, SortedSet<String>>> entry : this.walsById.entrySet()) {
422         String peerId = entry.getKey();
423         Map<String, SortedSet<String>> walsByPrefix = entry.getValue();
424         boolean existingPrefix = false;
425         for (Map.Entry<String, SortedSet<String>> walsEntry : walsByPrefix.entrySet()) {
426           SortedSet<String> wals = walsEntry.getValue();
427           if (this.sources.isEmpty()) {
428             // If there's no slaves, don't need to keep the old wals since
429             // we only consider the last one when a new slave comes in
430             wals.clear();
431           }
432           if (logPrefix.equals(walsEntry.getKey())) {
433             wals.add(logName);
434             existingPrefix = true;
435           }
436         }
437         if (!existingPrefix) {
438           // The new log belongs to a new group, add it into this peer
439           LOG.debug("Start tracking logs for wal group " + logPrefix + " for peer " + peerId);
440           SortedSet<String> wals = new TreeSet<String>();
441           wals.add(logName);
442           walsByPrefix.put(logPrefix, wals);
443         }
444       }
445     }
446   }
447 
448   void postLogRoll(Path newLog) throws IOException {
449     // This only updates the sources we own, not the recovered ones
450     for (ReplicationSourceInterface source : this.sources) {
451       source.enqueueLog(newLog);
452     }
453   }
454 
455   /**
456    * Factory method to create a replication source
457    * @param conf the configuration to use
458    * @param fs the file system to use
459    * @param manager the manager to use
460    * @param server the server object for this region server
461    * @param peerId the id of the peer cluster
462    * @return the created source
463    * @throws IOException
464    */
465   protected ReplicationSourceInterface getReplicationSource(final Configuration conf,
466       final FileSystem fs, final ReplicationSourceManager manager,
467       final ReplicationQueues replicationQueues, final ReplicationPeers replicationPeers,
468       final Server server, final String peerId, final UUID clusterId,
469       final ReplicationPeerConfig peerConfig, final ReplicationPeer replicationPeer)
470       throws IOException {
471     RegionServerCoprocessorHost rsServerHost = null;
472     TableDescriptors tableDescriptors = null;
473     if (server instanceof HRegionServer) {
474       rsServerHost = ((HRegionServer) server).getRegionServerCoprocessorHost();
475       tableDescriptors = ((HRegionServer) server).getTableDescriptors();
476     }
477     ReplicationSourceInterface src;
478     try {
479       @SuppressWarnings("rawtypes")
480       Class c = Class.forName(conf.get("replication.replicationsource.implementation",
481           ReplicationSource.class.getCanonicalName()));
482       src = (ReplicationSourceInterface) c.newInstance();
483     } catch (Exception e) {
484       LOG.warn("Passed replication source implementation throws errors, " +
485           "defaulting to ReplicationSource", e);
486       src = new ReplicationSource();
487     }
488 
489     ReplicationEndpoint replicationEndpoint = null;
490     try {
491       String replicationEndpointImpl = peerConfig.getReplicationEndpointImpl();
492       if (replicationEndpointImpl == null) {
493         // Default to HBase inter-cluster replication endpoint
494         replicationEndpointImpl = HBaseInterClusterReplicationEndpoint.class.getName();
495       }
496       @SuppressWarnings("rawtypes")
497       Class c = Class.forName(replicationEndpointImpl);
498       replicationEndpoint = (ReplicationEndpoint) c.newInstance();
499       if(rsServerHost != null) {
500         ReplicationEndpoint newReplicationEndPoint = rsServerHost
501             .postCreateReplicationEndPoint(replicationEndpoint);
502         if(newReplicationEndPoint != null) {
503           // Override the newly created endpoint from the hook with configured end point
504           replicationEndpoint = newReplicationEndPoint;
505         }
506       }
507     } catch (Exception e) {
508       LOG.warn("Passed replication endpoint implementation throws errors"
509           + " while initializing ReplicationSource for peer: " + peerId, e);
510       throw new IOException(e);
511     }
512 
513     MetricsSource metrics = new MetricsSource(peerId);
514     // init replication source
515     src.init(conf, fs, manager, replicationQueues, replicationPeers, server, peerId,
516       clusterId, replicationEndpoint, metrics);
517 
518     // init replication endpoint
519     replicationEndpoint.init(new ReplicationEndpoint.Context(replicationPeer.getConfiguration(),
520       fs, peerConfig, peerId, clusterId, replicationPeer, metrics, tableDescriptors, server));
521     return src;
522   }
523 
524   /**
525    * Transfer all the queues of the specified to this region server.
526    * First it tries to grab a lock and if it works it will move the
527    * znodes and finally will delete the old znodes.
528    *
529    * It creates one old source for any type of source of the old rs.
530    * @param rsZnode
531    */
532   private void transferQueues(String rsZnode) {
533     NodeFailoverWorker transfer =
534         new NodeFailoverWorker(rsZnode, this.replicationQueues, this.replicationPeers,
535             this.clusterId);
536     try {
537       this.executor.execute(transfer);
538     } catch (RejectedExecutionException ex) {
539       CompatibilitySingletonFactory.getInstance(MetricsReplicationSourceFactory.class)
540         .getGlobalSource().incrFailedRecoveryQueue();
541       LOG.info("Cancelling the transfer of " + rsZnode + " because of " + ex.getMessage());
542     }
543   }
544 
545   /**
546    * Clear the references to the specified old source
547    * @param src source to clear
548    */
549   public void closeRecoveredQueue(ReplicationSourceInterface src) {
550     LOG.info("Done with the recovered queue " + src.getPeerClusterZnode());
551     src.getSourceMetrics().clear();
552     this.oldsources.remove(src);
553     deleteSource(src.getPeerClusterZnode(), false);
554     this.walsByIdRecoveredQueues.remove(src.getPeerClusterZnode());
555   }
556 
557   /**
558    * Clear the references to the specified old source
559    * @param src source to clear
560    */
561   public void closeQueue(ReplicationSourceInterface src) {
562     LOG.info("Done with the queue " + src.getPeerClusterZnode());
563     src.getSourceMetrics().clear();
564     this.sources.remove(src);
565     deleteSource(src.getPeerClusterZnode(), true);
566     this.walsById.remove(src.getPeerClusterZnode());
567   }
568 
569   /**
570    * Thie method first deletes all the recovered sources for the specified
571    * id, then deletes the normal source (deleting all related data in ZK).
572    * @param id The id of the peer cluster
573    */
574   public void removePeer(String id) {
575     LOG.info("Closing the following queue " + id + ", currently have "
576         + sources.size() + " and another "
577         + oldsources.size() + " that were recovered");
578     String terminateMessage = "Replication stream was removed by a user";
579     List<ReplicationSourceInterface> oldSourcesToDelete =
580         new ArrayList<ReplicationSourceInterface>();
581     // synchronized on oldsources to avoid adding recovered source for the to-be-removed peer
582     // see NodeFailoverWorker.run
583     synchronized (oldsources) {
584       // First close all the recovered sources for this peer
585       for (ReplicationSourceInterface src : oldsources) {
586         if (id.equals(src.getPeerClusterId())) {
587           oldSourcesToDelete.add(src);
588         }
589       }
590       for (ReplicationSourceInterface src : oldSourcesToDelete) {
591         src.terminate(terminateMessage);
592         closeRecoveredQueue(src);
593       }
594     }
595     LOG.info("Number of deleted recovered sources for " + id + ": "
596         + oldSourcesToDelete.size());
597     // Now look for the one on this cluster
598     List<ReplicationSourceInterface> srcToRemove = new ArrayList<ReplicationSourceInterface>();
599     // synchronize on replicationPeers to avoid adding source for the to-be-removed peer
600     synchronized (this.replicationPeers) {
601       for (ReplicationSourceInterface src : this.sources) {
602         if (id.equals(src.getPeerClusterId())) {
603           srcToRemove.add(src);
604         }
605       }
606       if (srcToRemove.isEmpty()) {
607         LOG.error("The peer we wanted to remove is missing a ReplicationSourceInterface. " +
608             "This could mean that ReplicationSourceInterface initialization failed for this peer " +
609             "and that replication on this peer may not be caught up. peerId=" + id);
610       }
611       for (ReplicationSourceInterface toRemove : srcToRemove) {
612         toRemove.terminate(terminateMessage);
613         closeQueue(toRemove);
614       }
615       deleteSource(id, true);
616     }
617   }
618 
619   @Override
620   public void regionServerRemoved(String regionserver) {
621     transferQueues(regionserver);
622   }
623 
624   @Override
625   public void peerRemoved(String peerId) {
626     removePeer(peerId);
627     this.replicationQueues.removePeerFromHFileRefs(peerId);
628   }
629 
630   @Override
631   public void peerListChanged(List<String> peerIds) {
632     for (String id : peerIds) {
633       try {
634         boolean added = this.replicationPeers.peerAdded(id);
635         if (added) {
636           addSource(id);
637           if (replicationForBulkLoadDataEnabled) {
638             this.replicationQueues.addPeerToHFileRefs(id);
639           }
640         }
641       } catch (Exception e) {
642         LOG.error("Error while adding a new peer", e);
643       }
644     }
645   }
646 
647   /**
648    * Class responsible to setup new ReplicationSources to take care of the
649    * queues from dead region servers.
650    */
651   class NodeFailoverWorker extends Thread {
652 
653     private String rsZnode;
654     private final ReplicationQueues rq;
655     private final ReplicationPeers rp;
656     private final UUID clusterId;
657 
658     /**
659      * @param rsZnode
660      */
661     public NodeFailoverWorker(String rsZnode) {
662       this(rsZnode, replicationQueues, replicationPeers, ReplicationSourceManager.this.clusterId);
663     }
664 
665     public NodeFailoverWorker(String rsZnode, final ReplicationQueues replicationQueues,
666         final ReplicationPeers replicationPeers, final UUID clusterId) {
667       super("Failover-for-"+rsZnode);
668       this.rsZnode = rsZnode;
669       this.rq = replicationQueues;
670       this.rp = replicationPeers;
671       this.clusterId = clusterId;
672     }
673 
674     @Override
675     public void run() {
676       if (this.rq.isThisOurZnode(rsZnode)) {
677         return;
678       }
679       // Wait a bit before transferring the queues, we may be shutting down.
680       // This sleep may not be enough in some cases.
681       try {
682         Thread.sleep(sleepBeforeFailover + (long) (rand.nextFloat() * sleepBeforeFailover));
683       } catch (InterruptedException e) {
684         LOG.warn("Interrupted while waiting before transferring a queue.");
685         Thread.currentThread().interrupt();
686       }
687       // We try to lock that rs' queue directory
688       if (server.isStopped()) {
689         LOG.info("Not transferring queue since we are shutting down");
690         return;
691       }
692 
693       Map<String, SortedSet<String>> newQueues = new HashMap<>();
694       List<String> peers = rq.getUnClaimedQueueIds(rsZnode);
695       while (peers != null && !peers.isEmpty()) {
696         Pair<String, SortedSet<String>> peer = this.rq.claimQueue(rsZnode,
697             peers.get(rand.nextInt(peers.size())));
698         long sleep = sleepBeforeFailover/2;
699         if (peer != null) {
700           newQueues.put(peer.getFirst(), peer.getSecond());
701           sleep = sleepBeforeFailover;
702         }
703         try {
704           Thread.sleep(sleep);
705         } catch (InterruptedException e) {
706           LOG.warn("Interrupted while waiting before transferring a queue.");
707           Thread.currentThread().interrupt();
708         }
709         peers = rq.getUnClaimedQueueIds(rsZnode);
710       }
711       if (peers != null) {
712         rq.removeReplicatorIfQueueIsEmpty(rsZnode);
713       }
714 
715       // Copying over the failed queue is completed.
716       if (newQueues.isEmpty()) {
717         // We either didn't get the lock or the failed region server didn't have any outstanding
718         // WALs to replicate, so we are done.
719         return;
720       }
721 
722       for (Map.Entry<String, SortedSet<String>> entry : newQueues.entrySet()) {
723         String peerId = entry.getKey();
724         SortedSet<String> walsSet = entry.getValue();
725         try {
726           // there is not an actual peer defined corresponding to peerId for the failover.
727           ReplicationQueueInfo replicationQueueInfo = new ReplicationQueueInfo(peerId);
728           String actualPeerId = replicationQueueInfo.getPeerId();
729           ReplicationPeer peer = replicationPeers.getPeer(actualPeerId);
730           ReplicationPeerConfig peerConfig = null;
731           try {
732             peerConfig = replicationPeers.getReplicationPeerConfig(actualPeerId);
733           } catch (ReplicationException ex) {
734             LOG.warn("Received exception while getting replication peer config, skipping replay"
735                 + ex);
736           }
737           if (peer == null || peerConfig == null) {
738             LOG.warn("Skipping failover for peer:" + actualPeerId + " of node" + rsZnode);
739             replicationQueues.removeQueue(peerId);
740             continue;
741           }
742           if (server instanceof ReplicationSyncUp.DummyServer
743               && peer.getPeerState().equals(PeerState.DISABLED)) {
744             LOG.warn("Peer " + actualPeerId + " is disbaled. ReplicationSyncUp tool will skip "
745                 + "replicating data to this peer.");
746             continue;
747           }
748           // track sources in walsByIdRecoveredQueues
749           Map<String, SortedSet<String>> walsByGroup = new HashMap<String, SortedSet<String>>();
750           walsByIdRecoveredQueues.put(peerId, walsByGroup);
751           for (String wal : walsSet) {
752             String walPrefix = DefaultWALProvider.getWALPrefixFromWALName(wal);
753             SortedSet<String> wals = walsByGroup.get(walPrefix);
754             if (wals == null) {
755               wals = new TreeSet<String>();
756               walsByGroup.put(walPrefix, wals);
757             }
758             wals.add(wal);
759           }
760 
761           // enqueue sources
762           ReplicationSourceInterface src =
763               getReplicationSource(conf, fs, ReplicationSourceManager.this, this.rq, this.rp,
764                 server, peerId, this.clusterId, peerConfig, peer);
765           // synchronized on oldsources to avoid adding recovered source for the to-be-removed peer
766           // see removePeer
767           synchronized (oldsources) {
768             if (!this.rp.getPeerIds().contains(src.getPeerClusterId())) {
769               src.terminate("Recovered queue doesn't belong to any current peer");
770               closeRecoveredQueue(src);
771               continue;
772             }
773             oldsources.add(src);
774             for (String wal : walsSet) {
775               src.enqueueLog(new Path(oldLogDir, wal));
776             }
777             src.startup();
778           }
779         } catch (IOException e) {
780           // TODO manage it
781           LOG.error("Failed creating a source", e);
782         }
783       }
784     }
785   }
786 
787   /**
788    * Get the directory where wals are archived
789    * @return the directory where wals are archived
790    */
791   public Path getOldLogDir() {
792     return this.oldLogDir;
793   }
794 
795   /**
796    * Get the directory where wals are stored by their RSs
797    * @return the directory where wals are stored by their RSs
798    */
799   public Path getLogDir() {
800     return this.logDir;
801   }
802 
803   /**
804    * Get the handle on the local file system
805    * @return Handle on the local file system
806    */
807   public FileSystem getFs() {
808     return this.fs;
809   }
810 
811   /**
812    * Get the ReplicationPeers used by this ReplicationSourceManager
813    * @return the ReplicationPeers used by this ReplicationSourceManager
814    */
815   public ReplicationPeers getReplicationPeers() {return this.replicationPeers;}
816 
817   /**
818    * Get a string representation of all the sources' metrics
819    */
820   public String getStats() {
821     StringBuffer stats = new StringBuffer();
822     for (ReplicationSourceInterface source : sources) {
823       stats.append("Normal source for cluster " + source.getPeerClusterId() + ": ");
824       stats.append(source.getStats() + "\n");
825     }
826     for (ReplicationSourceInterface oldSource : oldsources) {
827       stats.append("Recovered source for cluster/machine(s) " + oldSource.getPeerClusterId()+": ");
828       stats.append(oldSource.getStats()+ "\n");
829     }
830     return stats.toString();
831   }
832 
833   public void addHFileRefs(TableName tableName, byte[] family, List<String> files)
834       throws ReplicationException {
835     for (ReplicationSourceInterface source : this.sources) {
836       source.addHFileRefs(tableName, family, files);
837     }
838   }
839 
840   public void cleanUpHFileRefs(String peerId, List<String> files) {
841     this.replicationQueues.removeHFileRefs(peerId, files);
842   }
843 }