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  package org.apache.hadoop.hbase.replication;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.SortedMap;
24  import java.util.SortedSet;
25  import java.util.TreeMap;
26  import java.util.TreeSet;
27  
28  import com.google.common.annotations.VisibleForTesting;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.Abortable;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.exceptions.DeserializationException;
36  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
37  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.hbase.util.Pair;
40  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
41  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
42  import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp;
43  import org.apache.zookeeper.KeeperException;
44  
45  /**
46   * This class provides an implementation of the ReplicationQueues interface using Zookeeper. The
47   * base znode that this class works at is the myQueuesZnode. The myQueuesZnode contains a list of
48   * all outstanding WAL files on this region server that need to be replicated. The myQueuesZnode is
49   * the regionserver name (a concatenation of the region server’s hostname, client port and start
50   * code). For example:
51   *
52   * /hbase/replication/rs/hostname.example.org,6020,1234
53   *
54   * Within this znode, the region server maintains a set of WAL replication queues. These queues are
55   * represented by child znodes named using there give queue id. For example:
56   *
57   * /hbase/replication/rs/hostname.example.org,6020,1234/1
58   * /hbase/replication/rs/hostname.example.org,6020,1234/2
59   *
60   * Each queue has one child znode for every WAL that still needs to be replicated. The value of
61   * these WAL child znodes is the latest position that has been replicated. This position is updated
62   * every time a WAL entry is replicated. For example:
63   *
64   * /hbase/replication/rs/hostname.example.org,6020,1234/1/23522342.23422 [VALUE: 254]
65   */
66  @InterfaceAudience.Private
67  public class ReplicationQueuesZKImpl extends ReplicationStateZKBase implements ReplicationQueues {
68  
69    /** Znode containing all replication queues for this region server. */
70    private String myQueuesZnode;
71    /** Name of znode we use to lock during failover */
72    public final static String RS_LOCK_ZNODE = "lock";
73  
74    private static final Log LOG = LogFactory.getLog(ReplicationQueuesZKImpl.class);
75  
76    public ReplicationQueuesZKImpl(final ZooKeeperWatcher zk, Configuration conf,
77        Abortable abortable) {
78      super(zk, conf, abortable);
79    }
80  
81    @Override
82    public void init(String serverName) throws ReplicationException {
83      this.myQueuesZnode = ZKUtil.joinZNode(this.queuesZNode, serverName);
84      try {
85        if (ZKUtil.checkExists(this.zookeeper, this.myQueuesZnode) < 0) {
86          ZKUtil.createWithParents(this.zookeeper, this.myQueuesZnode);
87        }
88      } catch (KeeperException e) {
89        throw new ReplicationException("Could not initialize replication queues.", e);
90      }
91      if (conf.getBoolean(HConstants.REPLICATION_BULKLOAD_ENABLE_KEY,
92        HConstants.REPLICATION_BULKLOAD_ENABLE_DEFAULT)) {
93        try {
94          if (ZKUtil.checkExists(this.zookeeper, this.hfileRefsZNode) < 0) {
95            ZKUtil.createWithParents(this.zookeeper, this.hfileRefsZNode);
96          }
97        } catch (KeeperException e) {
98          throw new ReplicationException("Could not initialize hfile references replication queue.",
99              e);
100       }
101     }
102   }
103 
104   @Override
105   public List<String> getListOfReplicators() throws ReplicationException {
106     try {
107       return super.getListOfReplicatorsZK();
108     } catch (KeeperException e) {
109       LOG.warn("getListOfReplicators() from ZK failed", e);
110       throw new ReplicationException("getListOfReplicators() from ZK failed", e);
111     }
112   }
113 
114   @Override
115   public void removeQueue(String queueId) {
116     try {
117       ZKUtil.deleteNodeRecursively(this.zookeeper, ZKUtil.joinZNode(this.myQueuesZnode, queueId));
118     } catch (KeeperException e) {
119       this.abortable.abort("Failed to delete queue (queueId=" + queueId + ")", e);
120     }
121   }
122 
123   @Override
124   public void addLog(String queueId, String filename) throws ReplicationException {
125     String znode = ZKUtil.joinZNode(this.myQueuesZnode, queueId);
126     znode = ZKUtil.joinZNode(znode, filename);
127     try {
128       ZKUtil.createWithParents(this.zookeeper, znode);
129     } catch (KeeperException e) {
130       throw new ReplicationException(
131           "Could not add log because znode could not be created. queueId=" + queueId
132               + ", filename=" + filename);
133     }
134   }
135 
136   @Override
137   public void removeLog(String queueId, String filename) {
138     try {
139       String znode = ZKUtil.joinZNode(this.myQueuesZnode, queueId);
140       znode = ZKUtil.joinZNode(znode, filename);
141       ZKUtil.deleteNode(this.zookeeper, znode);
142     } catch (KeeperException e) {
143       this.abortable.abort("Failed to remove wal from queue (queueId=" + queueId + ", filename="
144           + filename + ")", e);
145     }
146   }
147 
148   @Override
149   public void setLogPosition(String queueId, String filename, long position) {
150     try {
151       String znode = ZKUtil.joinZNode(this.myQueuesZnode, queueId);
152       znode = ZKUtil.joinZNode(znode, filename);
153       // Why serialize String of Long and not Long as bytes?
154       ZKUtil.setData(this.zookeeper, znode, ZKUtil.positionToByteArray(position));
155     } catch (KeeperException e) {
156       this.abortable.abort("Failed to write replication wal position (filename=" + filename
157           + ", position=" + position + ")", e);
158     }
159   }
160 
161   @Override
162   public long getLogPosition(String queueId, String filename) throws ReplicationException {
163     String clusterZnode = ZKUtil.joinZNode(this.myQueuesZnode, queueId);
164     String znode = ZKUtil.joinZNode(clusterZnode, filename);
165     byte[] bytes = null;
166     try {
167       bytes = ZKUtil.getData(this.zookeeper, znode);
168     } catch (KeeperException e) {
169       throw new ReplicationException("Internal Error: could not get position in log for queueId="
170           + queueId + ", filename=" + filename, e);
171     } catch (InterruptedException e) {
172       Thread.currentThread().interrupt();
173       return 0;
174     }
175     try {
176       return ZKUtil.parseWALPositionFrom(bytes);
177     } catch (DeserializationException de) {
178       LOG.warn("Failed to parse WALPosition for queueId=" + queueId + " and wal=" + filename
179           + " znode content, continuing.");
180     }
181     // if we can not parse the position, start at the beginning of the wal file
182     // again
183     return 0;
184   }
185 
186   @Override
187   public boolean isThisOurZnode(String znode) {
188     return ZKUtil.joinZNode(this.queuesZNode, znode).equals(this.myQueuesZnode);
189   }
190 
191   @Override
192   public void removeAllQueues() {
193     try {
194       ZKUtil.deleteNodeRecursively(this.zookeeper, this.myQueuesZnode);
195     } catch (KeeperException e) {
196       // if the znode is already expired, don't bother going further
197       if (e instanceof KeeperException.SessionExpiredException) {
198         return;
199       }
200       this.abortable.abort("Failed to delete replication queues for region server: "
201           + this.myQueuesZnode, e);
202     }
203   }
204 
205   @Override
206   public List<String> getLogsInQueue(String queueId) {
207     String znode = ZKUtil.joinZNode(this.myQueuesZnode, queueId);
208     List<String> result = null;
209     try {
210       result = ZKUtil.listChildrenNoWatch(this.zookeeper, znode);
211     } catch (KeeperException e) {
212       this.abortable.abort("Failed to get list of wals for queueId=" + queueId, e);
213     }
214     return result;
215   }
216 
217   @Override
218   public List<String> getAllQueues() {
219     List<String> listOfQueues = null;
220     try {
221       listOfQueues = ZKUtil.listChildrenNoWatch(this.zookeeper, this.myQueuesZnode);
222     } catch (KeeperException e) {
223       this.abortable.abort("Failed to get a list of queues for region server: "
224           + this.myQueuesZnode, e);
225     }
226     return listOfQueues;
227   }
228 
229   @Override
230   public boolean isThisOurRegionServer(String regionserver) {
231     return ZKUtil.joinZNode(this.queuesZNode, regionserver).equals(this.myQueuesZnode);
232   }
233 
234   @Override
235   public List<String> getUnClaimedQueueIds(String regionserver) {
236     if (isThisOurRegionServer(regionserver)) {
237       return null;
238     }
239     String rsZnodePath = ZKUtil.joinZNode(this.queuesZNode, regionserver);
240     List<String> queues = null;
241     try {
242       queues = ZKUtil.listChildrenNoWatch(this.zookeeper, rsZnodePath);
243     } catch (KeeperException e) {
244       this.abortable.abort("Failed to getUnClaimedQueueIds for " + regionserver, e);
245     }
246     if (queues != null) {
247       queues.remove(RS_LOCK_ZNODE);
248     }
249     return queues;
250   }
251 
252   @Override
253   public Pair<String, SortedSet<String>> claimQueue(String regionserver, String queueId) {
254     if (conf.getBoolean(HConstants.ZOOKEEPER_USEMULTI, true)) {
255       LOG.info("Atomically moving " + regionserver + "/" + queueId + "'s WALs to my queue");
256       return moveQueueUsingMulti(regionserver, queueId);
257     } else {
258       LOG.info("Moving " + regionserver + "/" + queueId + "'s wals to my queue");
259       if (!lockOtherRS(regionserver)) {
260         LOG.info("Can not take the lock now");
261         return null;
262       }
263       Pair<String, SortedSet<String>> newQueues;
264       try {
265         newQueues = copyQueueFromLockedRS(regionserver, queueId);
266         removeQueueFromLockedRS(regionserver, queueId);
267       } finally {
268         unlockOtherRS(regionserver);
269       }
270       return newQueues;
271     }
272   }
273 
274   private void removeQueueFromLockedRS(String znode, String peerId) {
275     String nodePath = ZKUtil.joinZNode(this.queuesZNode, znode);
276     String peerPath = ZKUtil.joinZNode(nodePath, peerId);
277     try {
278       ZKUtil.deleteNodeRecursively(this.zookeeper, peerPath);
279     } catch (KeeperException e) {
280       LOG.warn("Remove copied queue failed", e);
281     }
282   }
283 
284   @Override
285   public void removeReplicatorIfQueueIsEmpty(String regionserver) {
286     String rsPath = ZKUtil.joinZNode(this.queuesZNode, regionserver);
287     try {
288       List<String> list = ZKUtil.listChildrenNoWatch(this.zookeeper, rsPath);
289       if (list != null && list.size() == 0){
290         ZKUtil.deleteNode(this.zookeeper, rsPath);
291       }
292     } catch (KeeperException e) {
293       LOG.warn("Got error while removing replicator", e);
294     }
295   }
296 
297   /**
298    * Try to set a lock in another region server's znode.
299    * @param znode the server names of the other server
300    * @return true if the lock was acquired, false in every other cases
301    */
302   @VisibleForTesting
303   public boolean lockOtherRS(String znode) {
304     try {
305       String parent = ZKUtil.joinZNode(this.queuesZNode, znode);
306       if (parent.equals(this.myQueuesZnode)) {
307         LOG.warn("Won't lock because this is us, we're dead!");
308         return false;
309       }
310       String p = ZKUtil.joinZNode(parent, RS_LOCK_ZNODE);
311       ZKUtil.createAndWatch(this.zookeeper, p, lockToByteArray(this.myQueuesZnode));
312     } catch (KeeperException e) {
313       // This exception will pop up if the znode under which we're trying to
314       // create the lock is already deleted by another region server, meaning
315       // that the transfer already occurred.
316       // NoNode => transfer is done and znodes are already deleted
317       // NodeExists => lock znode already created by another RS
318       if (e instanceof KeeperException.NoNodeException
319           || e instanceof KeeperException.NodeExistsException) {
320         LOG.info("Won't transfer the queue," + " another RS took care of it because of: "
321             + e.getMessage());
322       } else {
323         LOG.info("Failed lock other rs", e);
324       }
325       return false;
326     }
327     return true;
328   }
329 
330   public String getLockZNode(String znode) {
331     return this.queuesZNode + "/" + znode + "/" + RS_LOCK_ZNODE;
332   }
333 
334   @VisibleForTesting
335   public boolean checkLockExists(String znode) throws KeeperException {
336     return ZKUtil.checkExists(zookeeper, getLockZNode(znode)) >= 0;
337   }
338 
339   private void unlockOtherRS(String znode){
340     String parent = ZKUtil.joinZNode(this.queuesZNode, znode);
341     String p = ZKUtil.joinZNode(parent, RS_LOCK_ZNODE);
342     try {
343       ZKUtil.deleteNode(this.zookeeper, p);
344     } catch (KeeperException e) {
345       this.abortable.abort("Remove lock failed", e);
346     }
347   }
348 
349   /**
350    * Delete all the replication queues for a given region server.
351    * @param regionserverZnode The znode of the region server to delete.
352    */
353   private void deleteAnotherRSQueues(String regionserverZnode) {
354     String fullpath = ZKUtil.joinZNode(this.queuesZNode, regionserverZnode);
355     try {
356       List<String> clusters = ZKUtil.listChildrenNoWatch(this.zookeeper, fullpath);
357       for (String cluster : clusters) {
358         // No need to delete, it will be deleted later.
359         if (cluster.equals(RS_LOCK_ZNODE)) {
360           continue;
361         }
362         String fullClusterPath = ZKUtil.joinZNode(fullpath, cluster);
363         ZKUtil.deleteNodeRecursively(this.zookeeper, fullClusterPath);
364       }
365       // Finish cleaning up
366       ZKUtil.deleteNodeRecursively(this.zookeeper, fullpath);
367     } catch (KeeperException e) {
368       if (e instanceof KeeperException.NoNodeException
369           || e instanceof KeeperException.NotEmptyException) {
370         // Testing a special case where another region server was able to
371         // create a lock just after we deleted it, but then was also able to
372         // delete the RS znode before us or its lock znode is still there.
373         if (e.getPath().equals(fullpath)) {
374           return;
375         }
376       }
377       this.abortable.abort("Failed to delete replication queues for region server: "
378           + regionserverZnode, e);
379     }
380   }
381 
382   /**
383    * It "atomically" copies all the wals queues from another region server and returns them all
384    * sorted per peer cluster (appended with the dead server's znode).
385    * @param znode pertaining to the region server to copy the queues from
386    */
387   private Pair<String, SortedSet<String>> moveQueueUsingMulti(String znode, String peerId) {
388     try {
389       // hbase/replication/rs/deadrs
390       String deadRSZnodePath = ZKUtil.joinZNode(this.queuesZNode, znode);
391       List<ZKUtilOp> listOfOps = new ArrayList<>();
392       ReplicationQueueInfo replicationQueueInfo = new ReplicationQueueInfo(peerId);
393       if (!peerExists(replicationQueueInfo.getPeerId())) {
394         // the orphaned queues must be moved, otherwise the delete op of dead rs will fail,
395         // this will cause the whole multi op fail.
396         // NodeFailoverWorker will skip the orphaned queues.
397         LOG.warn("Peer " + peerId +
398             " didn't exist, will move its queue to avoid the failure of multi op");
399       }
400       String newPeerId = peerId + "-" + znode;
401       String newPeerZnode = ZKUtil.joinZNode(this.myQueuesZnode, newPeerId);
402       // check the logs queue for the old peer cluster
403       String oldClusterZnode = ZKUtil.joinZNode(deadRSZnodePath, peerId);
404       List<String> wals = ZKUtil.listChildrenNoWatch(this.zookeeper, oldClusterZnode);
405       SortedSet<String> logQueue = new TreeSet<>();
406       if (wals == null || wals.size() == 0) {
407         listOfOps.add(ZKUtilOp.deleteNodeFailSilent(oldClusterZnode));
408       } else {
409         // create the new cluster znode
410         ZKUtilOp op = ZKUtilOp.createAndFailSilent(newPeerZnode, HConstants.EMPTY_BYTE_ARRAY);
411         listOfOps.add(op);
412         // get the offset of the logs and set it to new znodes
413         for (String wal : wals) {
414           String oldWalZnode = ZKUtil.joinZNode(oldClusterZnode, wal);
415           byte[] logOffset = ZKUtil.getData(this.zookeeper, oldWalZnode);
416           LOG.debug("Creating " + wal + " with data " + Bytes.toString(logOffset));
417           String newLogZnode = ZKUtil.joinZNode(newPeerZnode, wal);
418           listOfOps.add(ZKUtilOp.createAndFailSilent(newLogZnode, logOffset));
419           listOfOps.add(ZKUtilOp.deleteNodeFailSilent(oldWalZnode));
420           logQueue.add(wal);
421         }
422         // add delete op for peer
423         listOfOps.add(ZKUtilOp.deleteNodeFailSilent(oldClusterZnode));
424 
425         if (LOG.isTraceEnabled())
426           LOG.trace(" The multi list size is: " + listOfOps.size());
427       }
428       ZKUtil.multiOrSequential(this.zookeeper, listOfOps, false);
429       if (LOG.isTraceEnabled())
430         LOG.trace("Atomically moved the dead regionserver logs. ");
431       return new Pair<>(newPeerId, logQueue);
432     } catch (KeeperException e) {
433       // Multi call failed; it looks like some other regionserver took away the logs.
434       LOG.warn("Got exception in copyQueuesFromRSUsingMulti: ", e);
435     } catch (InterruptedException e) {
436       LOG.warn("Got exception in copyQueuesFromRSUsingMulti: ", e);
437       Thread.currentThread().interrupt();
438     }
439     return null;
440   }
441 
442   /**
443    * This methods moves all the wals queues from another region server and returns them all sorted
444    * per peer cluster (appended with the dead server's znode)
445    * @param znode server names to copy
446    * @return all wals for the peer of that cluster, null if an error occurred
447    */
448   private Pair<String, SortedSet<String>> copyQueueFromLockedRS(String znode, String peerId) {
449     // TODO this method isn't atomic enough, we could start copying and then
450     // TODO fail for some reason and we would end up with znodes we don't want.
451     try {
452       String nodePath = ZKUtil.joinZNode(this.queuesZNode, znode);
453       ReplicationQueueInfo replicationQueueInfo = new ReplicationQueueInfo(peerId);
454       String clusterPath = ZKUtil.joinZNode(nodePath, peerId);
455       if (!peerExists(replicationQueueInfo.getPeerId())) {
456         LOG.warn("Peer " + peerId + " didn't exist, skipping the replay");
457         // Protection against moving orphaned queues
458         return null;
459       }
460       // We add the name of the recovered RS to the new znode, we can even
461       // do that for queues that were recovered 10 times giving a znode like
462       // number-startcode-number-otherstartcode-number-anotherstartcode-etc
463       String newCluster = peerId + "-" + znode;
464       String newClusterZnode = ZKUtil.joinZNode(this.myQueuesZnode, newCluster);
465 
466       List<String> wals = ZKUtil.listChildrenNoWatch(this.zookeeper, clusterPath);
467       // That region server didn't have anything to replicate for this cluster
468       if (wals == null || wals.size() == 0) {
469         return null;
470       }
471       ZKUtil.createNodeIfNotExistsAndWatch(this.zookeeper, newClusterZnode,
472           HConstants.EMPTY_BYTE_ARRAY);
473       SortedSet<String> logQueue = new TreeSet<>();
474       for (String wal : wals) {
475         String z = ZKUtil.joinZNode(clusterPath, wal);
476         byte[] positionBytes = ZKUtil.getData(this.zookeeper, z);
477         long position = 0;
478         try {
479           position = ZKUtil.parseWALPositionFrom(positionBytes);
480         } catch (DeserializationException e) {
481           LOG.warn("Failed parse of wal position from the following znode: " + z
482               + ", Exception: " + e);
483         }
484         LOG.debug("Creating " + wal + " with data " + position);
485         String child = ZKUtil.joinZNode(newClusterZnode, wal);
486         // Position doesn't actually change, we are just deserializing it for
487         // logging, so just use the already serialized version
488         ZKUtil.createNodeIfNotExistsAndWatch(this.zookeeper, child, positionBytes);
489         logQueue.add(wal);
490       }
491       return new Pair<>(newCluster, logQueue);
492     } catch (KeeperException e) {
493       LOG.warn("Got exception in copyQueueFromLockedRS: "+
494         " Possible problem: check if znode size exceeds jute.maxBuffer value. "
495           + "If so, increase it for both client and server side." ,e);
496 
497     } catch (InterruptedException e) {
498       LOG.warn(e);
499       Thread.currentThread().interrupt();
500     }
501     return null;
502   }
503 
504   /**
505    * @param lockOwner
506    * @return Serialized protobuf of <code>lockOwner</code> with pb magic prefix prepended suitable
507    *         for use as content of an replication lock during region server fail over.
508    */
509   static byte[] lockToByteArray(final String lockOwner) {
510     byte[] bytes =
511         ZooKeeperProtos.ReplicationLock.newBuilder().setLockOwner(lockOwner).build().toByteArray();
512     return ProtobufUtil.prependPBMagic(bytes);
513   }
514 
515   @Override
516   public void addHFileRefs(String peerId, List<String> files) throws ReplicationException {
517     String peerZnode = ZKUtil.joinZNode(this.hfileRefsZNode, peerId);
518     boolean debugEnabled = LOG.isDebugEnabled();
519     if (debugEnabled) {
520       LOG.debug("Adding hfile references " + files + " in queue " + peerZnode);
521     }
522     List<ZKUtilOp> listOfOps = new ArrayList<ZKUtil.ZKUtilOp>();
523     int size = files.size();
524     for (int i = 0; i < size; i++) {
525       listOfOps.add(ZKUtilOp.createAndFailSilent(ZKUtil.joinZNode(peerZnode, files.get(i)),
526         HConstants.EMPTY_BYTE_ARRAY));
527     }
528     if (debugEnabled) {
529       LOG.debug(" The multi list size for adding hfile references in zk for node " + peerZnode
530           + " is " + listOfOps.size());
531     }
532     try {
533       ZKUtil.multiOrSequential(this.zookeeper, listOfOps, true);
534     } catch (KeeperException e) {
535       throw new ReplicationException("Failed to create hfile reference znode=" + e.getPath(), e);
536     }
537   }
538 
539   @Override
540   public void removeHFileRefs(String peerId, List<String> files) {
541     String peerZnode = ZKUtil.joinZNode(this.hfileRefsZNode, peerId);
542     boolean debugEnabled = LOG.isDebugEnabled();
543     if (debugEnabled) {
544       LOG.debug("Removing hfile references " + files + " from queue " + peerZnode);
545     }
546     List<ZKUtilOp> listOfOps = new ArrayList<ZKUtil.ZKUtilOp>();
547     int size = files.size();
548     for (int i = 0; i < size; i++) {
549       listOfOps.add(ZKUtilOp.deleteNodeFailSilent(ZKUtil.joinZNode(peerZnode, files.get(i))));
550     }
551     if (debugEnabled) {
552       LOG.debug(" The multi list size for removing hfile references in zk for node " + peerZnode
553           + " is " + listOfOps.size());
554     }
555     try {
556       ZKUtil.multiOrSequential(this.zookeeper, listOfOps, true);
557     } catch (KeeperException e) {
558       LOG.error("Failed to remove hfile reference znode=" + e.getPath(), e);
559     }
560   }
561 
562   @Override
563   public void addPeerToHFileRefs(String peerId) throws ReplicationException {
564     String peerZnode = ZKUtil.joinZNode(this.hfileRefsZNode, peerId);
565     try {
566       if (ZKUtil.checkExists(this.zookeeper, peerZnode) == -1) {
567         LOG.info("Adding peer " + peerId + " to hfile reference queue.");
568         ZKUtil.createWithParents(this.zookeeper, peerZnode);
569       }
570     } catch (KeeperException e) {
571       throw new ReplicationException("Failed to add peer " + peerId + " to hfile reference queue.",
572           e);
573     }
574   }
575 
576   @Override
577   public void removePeerFromHFileRefs(String peerId) {
578     final String peerZnode = ZKUtil.joinZNode(this.hfileRefsZNode, peerId);
579     try {
580       if (ZKUtil.checkExists(this.zookeeper, peerZnode) == -1) {
581         if (LOG.isDebugEnabled()) {
582           LOG.debug("Peer " + peerZnode + " not found in hfile reference queue.");
583         }
584         return;
585       } else {
586         LOG.info("Removing peer " + peerZnode + " from hfile reference queue.");
587         ZKUtil.deleteNodeRecursively(this.zookeeper, peerZnode);
588       }
589     } catch (KeeperException e) {
590       LOG.error("Ignoring the exception to remove peer " + peerId + " from hfile reference queue.",
591         e);
592     }
593   }
594 }