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