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     // synchronize on replicationPeers to avoid adding source for the to-be-removed peer
409     synchronized (replicationPeers) {
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(conf, replicationPeer.getConfiguration(),
520       fs, peerId, clusterId, replicationPeer, metrics, tableDescriptors, server));
521 
522     return src;
523   }
524 
525   /**
526    * Transfer all the queues of the specified to this region server.
527    * First it tries to grab a lock and if it works it will move the
528    * znodes and finally will delete the old znodes.
529    *
530    * It creates one old source for any type of source of the old rs.
531    * @param rsZnode
532    */
533   private void transferQueues(String rsZnode) {
534     NodeFailoverWorker transfer =
535         new NodeFailoverWorker(rsZnode, this.replicationQueues, this.replicationPeers,
536             this.clusterId);
537     try {
538       this.executor.execute(transfer);
539     } catch (RejectedExecutionException ex) {
540       CompatibilitySingletonFactory.getInstance(MetricsReplicationSourceFactory.class)
541         .getGlobalSource().incrFailedRecoveryQueue();
542       LOG.info("Cancelling the transfer of " + rsZnode + " because of " + ex.getMessage());
543     }
544   }
545 
546   /**
547    * Clear the references to the specified old source
548    * @param src source to clear
549    */
550   public void closeRecoveredQueue(ReplicationSourceInterface src) {
551     LOG.info("Done with the recovered queue " + src.getPeerClusterZnode());
552     src.getSourceMetrics().clear();
553     this.oldsources.remove(src);
554     deleteSource(src.getPeerClusterZnode(), false);
555     this.walsByIdRecoveredQueues.remove(src.getPeerClusterZnode());
556   }
557 
558   /**
559    * Clear the references to the specified old source
560    * @param src source to clear
561    */
562   public void closeQueue(ReplicationSourceInterface src) {
563     LOG.info("Done with the queue " + src.getPeerClusterZnode());
564     src.getSourceMetrics().clear();
565     this.sources.remove(src);
566     deleteSource(src.getPeerClusterZnode(), true);
567     this.walsById.remove(src.getPeerClusterZnode());
568   }
569 
570   /**
571    * Thie method first deletes all the recovered sources for the specified
572    * id, then deletes the normal source (deleting all related data in ZK).
573    * @param id The id of the peer cluster
574    */
575   public void removePeer(String id) {
576     LOG.info("Closing the following queue " + id + ", currently have "
577         + sources.size() + " and another "
578         + oldsources.size() + " that were recovered");
579     String terminateMessage = "Replication stream was removed by a user";
580     List<ReplicationSourceInterface> oldSourcesToDelete =
581         new ArrayList<ReplicationSourceInterface>();
582     // synchronized on oldsources to avoid adding recovered source for the to-be-removed peer
583     // see NodeFailoverWorker.run
584     synchronized (oldsources) {
585       // First close all the recovered sources for this peer
586       for (ReplicationSourceInterface src : oldsources) {
587         if (id.equals(src.getPeerClusterId())) {
588           oldSourcesToDelete.add(src);
589         }
590       }
591       for (ReplicationSourceInterface src : oldSourcesToDelete) {
592         src.terminate(terminateMessage);
593         closeRecoveredQueue(src);
594       }
595     }
596     LOG.info("Number of deleted recovered sources for " + id + ": "
597         + oldSourcesToDelete.size());
598     // Now look for the one on this cluster
599     List<ReplicationSourceInterface> srcToRemove = new ArrayList<ReplicationSourceInterface>();
600     // synchronize on replicationPeers to avoid adding source for the to-be-removed peer
601     synchronized (this.replicationPeers) {
602       for (ReplicationSourceInterface src : this.sources) {
603         if (id.equals(src.getPeerClusterId())) {
604           srcToRemove.add(src);
605         }
606       }
607       if (srcToRemove.isEmpty()) {
608         LOG.error("The peer we wanted to remove is missing a ReplicationSourceInterface. " +
609             "This could mean that ReplicationSourceInterface initialization failed for this peer " +
610             "and that replication on this peer may not be caught up. peerId=" + id);
611       }
612       for (ReplicationSourceInterface toRemove : srcToRemove) {
613         toRemove.terminate(terminateMessage);
614         closeQueue(toRemove);
615       }
616       deleteSource(id, true);
617     }
618   }
619 
620   @Override
621   public void regionServerRemoved(String regionserver) {
622     transferQueues(regionserver);
623   }
624 
625   @Override
626   public void peerRemoved(String peerId) {
627     removePeer(peerId);
628     this.replicationQueues.removePeerFromHFileRefs(peerId);
629   }
630 
631   @Override
632   public void peerListChanged(List<String> peerIds) {
633     for (String id : peerIds) {
634       try {
635         boolean added = this.replicationPeers.peerAdded(id);
636         if (added) {
637           addSource(id);
638           if (replicationForBulkLoadDataEnabled) {
639             this.replicationQueues.addPeerToHFileRefs(id);
640           }
641         }
642       } catch (Exception e) {
643         LOG.error("Error while adding a new peer", e);
644       }
645     }
646   }
647 
648   /**
649    * Class responsible to setup new ReplicationSources to take care of the
650    * queues from dead region servers.
651    */
652   class NodeFailoverWorker extends Thread {
653 
654     private String rsZnode;
655     private final ReplicationQueues rq;
656     private final ReplicationPeers rp;
657     private final UUID clusterId;
658 
659     /**
660      * @param rsZnode
661      */
662     public NodeFailoverWorker(String rsZnode) {
663       this(rsZnode, replicationQueues, replicationPeers, ReplicationSourceManager.this.clusterId);
664     }
665 
666     public NodeFailoverWorker(String rsZnode, final ReplicationQueues replicationQueues,
667         final ReplicationPeers replicationPeers, final UUID clusterId) {
668       super("Failover-for-"+rsZnode);
669       this.rsZnode = rsZnode;
670       this.rq = replicationQueues;
671       this.rp = replicationPeers;
672       this.clusterId = clusterId;
673     }
674 
675     @Override
676     public void run() {
677       if (this.rq.isThisOurZnode(rsZnode)) {
678         return;
679       }
680       // Wait a bit before transferring the queues, we may be shutting down.
681       // This sleep may not be enough in some cases.
682       try {
683         Thread.sleep(sleepBeforeFailover + (long) (rand.nextFloat() * sleepBeforeFailover));
684       } catch (InterruptedException e) {
685         LOG.warn("Interrupted while waiting before transferring a queue.");
686         Thread.currentThread().interrupt();
687       }
688       // We try to lock that rs' queue directory
689       if (server.isStopped()) {
690         LOG.info("Not transferring queue since we are shutting down");
691         return;
692       }
693 
694       Map<String, SortedSet<String>> newQueues = new HashMap<>();
695       List<String> peers = rq.getUnClaimedQueueIds(rsZnode);
696       while (peers != null && !peers.isEmpty()) {
697         Pair<String, SortedSet<String>> peer = this.rq.claimQueue(rsZnode,
698             peers.get(rand.nextInt(peers.size())));
699         long sleep = sleepBeforeFailover/2;
700         if (peer != null) {
701           newQueues.put(peer.getFirst(), peer.getSecond());
702           sleep = sleepBeforeFailover;
703         }
704         try {
705           Thread.sleep(sleep);
706         } catch (InterruptedException e) {
707           LOG.warn("Interrupted while waiting before transferring a queue.");
708           Thread.currentThread().interrupt();
709         }
710         peers = rq.getUnClaimedQueueIds(rsZnode);
711       }
712       if (peers != null) {
713         rq.removeReplicatorIfQueueIsEmpty(rsZnode);
714       }
715 
716       // Copying over the failed queue is completed.
717       if (newQueues.isEmpty()) {
718         // We either didn't get the lock or the failed region server didn't have any outstanding
719         // WALs to replicate, so we are done.
720         return;
721       }
722 
723       for (Map.Entry<String, SortedSet<String>> entry : newQueues.entrySet()) {
724         String peerId = entry.getKey();
725         SortedSet<String> walsSet = entry.getValue();
726         try {
727           // there is not an actual peer defined corresponding to peerId for the failover.
728           ReplicationQueueInfo replicationQueueInfo = new ReplicationQueueInfo(peerId);
729           String actualPeerId = replicationQueueInfo.getPeerId();
730           ReplicationPeer peer = replicationPeers.getPeer(actualPeerId);
731           ReplicationPeerConfig peerConfig = null;
732           try {
733             peerConfig = replicationPeers.getReplicationPeerConfig(actualPeerId);
734           } catch (ReplicationException ex) {
735             LOG.warn("Received exception while getting replication peer config, skipping replay"
736                 + ex);
737           }
738           if (peer == null || peerConfig == null) {
739             LOG.warn("Skipping failover for peer:" + actualPeerId + " of node" + rsZnode);
740             replicationQueues.removeQueue(peerId);
741             continue;
742           }
743           if (server instanceof ReplicationSyncUp.DummyServer
744               && peer.getPeerState().equals(PeerState.DISABLED)) {
745             LOG.warn("Peer " + actualPeerId + " is disbaled. ReplicationSyncUp tool will skip "
746                 + "replicating data to this peer.");
747             continue;
748           }
749           // track sources in walsByIdRecoveredQueues
750           Map<String, SortedSet<String>> walsByGroup = new HashMap<String, SortedSet<String>>();
751           walsByIdRecoveredQueues.put(peerId, walsByGroup);
752           for (String wal : walsSet) {
753             String walPrefix = DefaultWALProvider.getWALPrefixFromWALName(wal);
754             SortedSet<String> wals = walsByGroup.get(walPrefix);
755             if (wals == null) {
756               wals = new TreeSet<String>();
757               walsByGroup.put(walPrefix, wals);
758             }
759             wals.add(wal);
760           }
761 
762           // enqueue sources
763           ReplicationSourceInterface src =
764               getReplicationSource(conf, fs, ReplicationSourceManager.this, this.rq, this.rp,
765                 server, peerId, this.clusterId, peerConfig, peer);
766           // synchronized on oldsources to avoid adding recovered source for the to-be-removed peer
767           // see removePeer
768           synchronized (oldsources) {
769             if (!this.rp.getPeerIds().contains(src.getPeerClusterId())) {
770               src.terminate("Recovered queue doesn't belong to any current peer");
771               closeRecoveredQueue(src);
772               continue;
773             }
774             oldsources.add(src);
775             for (String wal : walsSet) {
776               src.enqueueLog(new Path(oldLogDir, wal));
777             }
778             src.startup();
779           }
780         } catch (IOException e) {
781           // TODO manage it
782           LOG.error("Failed creating a source", e);
783         }
784       }
785     }
786   }
787 
788   /**
789    * Get the directory where wals are archived
790    * @return the directory where wals are archived
791    */
792   public Path getOldLogDir() {
793     return this.oldLogDir;
794   }
795 
796   /**
797    * Get the directory where wals are stored by their RSs
798    * @return the directory where wals are stored by their RSs
799    */
800   public Path getLogDir() {
801     return this.logDir;
802   }
803 
804   /**
805    * Get the handle on the local file system
806    * @return Handle on the local file system
807    */
808   public FileSystem getFs() {
809     return this.fs;
810   }
811 
812   /**
813    * Get the ReplicationPeers used by this ReplicationSourceManager
814    * @return the ReplicationPeers used by this ReplicationSourceManager
815    */
816   public ReplicationPeers getReplicationPeers() {return this.replicationPeers;}
817 
818   /**
819    * Get a string representation of all the sources' metrics
820    */
821   public String getStats() {
822     StringBuffer stats = new StringBuffer();
823     for (ReplicationSourceInterface source : sources) {
824       stats.append("Normal source for cluster " + source.getPeerClusterId() + ": ");
825       stats.append(source.getStats() + "\n");
826     }
827     for (ReplicationSourceInterface oldSource : oldsources) {
828       stats.append("Recovered source for cluster/machine(s) " + oldSource.getPeerClusterId()+": ");
829       stats.append(oldSource.getStats()+ "\n");
830     }
831     return stats.toString();
832   }
833 
834   public void addHFileRefs(TableName tableName, byte[] family, List<Pair<Path, Path>> pairs)
835       throws ReplicationException {
836     for (ReplicationSourceInterface source : this.sources) {
837       source.addHFileRefs(tableName, family, pairs);
838     }
839   }
840 
841   public void cleanUpHFileRefs(String peerId, List<String> files) {
842     this.replicationQueues.removeHFileRefs(peerId, files);
843   }
844 }