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.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.TreeMap;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ConcurrentMap;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.hbase.HBaseConfiguration;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.hbase.Abortable;
37  import org.apache.hadoop.hbase.CompoundConfiguration;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.exceptions.DeserializationException;
40  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
41  import org.apache.hadoop.hbase.replication.ReplicationPeer.PeerState;
42  import org.apache.hadoop.hbase.util.Pair;
43  import org.apache.hadoop.hbase.zookeeper.ZKConfig;
44  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
45  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
46  import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp;
47  import org.apache.zookeeper.KeeperException;
48  
49  /**
50   * This class provides an implementation of the ReplicationPeers interface using Zookeeper. The
51   * peers znode contains a list of all peer replication clusters and the current replication state of
52   * those clusters. It has one child peer znode for each peer cluster. The peer znode is named with
53   * the cluster id provided by the user in the HBase shell. The value of the peer znode contains the
54   * peers cluster key provided by the user in the HBase Shell. The cluster key contains a list of
55   * zookeeper quorum peers, the client port for the zookeeper quorum, and the base znode for HBase.
56   * For example:
57   *
58   *  /hbase/replication/peers/1 [Value: zk1.host.com,zk2.host.com,zk3.host.com:2181:/hbase]
59   *  /hbase/replication/peers/2 [Value: zk5.host.com,zk6.host.com,zk7.host.com:2181:/hbase]
60   *
61   * Each of these peer znodes has a child znode that indicates whether or not replication is enabled
62   * on that peer cluster. These peer-state znodes do not have child znodes and simply contain a
63   * boolean value (i.e. ENABLED or DISABLED). This value is read/maintained by the
64   * ReplicationPeer.PeerStateTracker class. For example:
65   *
66   * /hbase/replication/peers/1/peer-state [Value: ENABLED]
67   *
68   * Each of these peer znodes has a child znode that indicates which data will be replicated
69   * to the peer cluster. These peer-tableCFs znodes do not have child znodes and only have a
70   * table/cf list config. This value is read/maintained by the ReplicationPeer.TableCFsTracker
71   * class. For example:
72   *
73   * /hbase/replication/peers/1/tableCFs [Value: "table1; table2:cf1,cf3; table3:cfx,cfy"]
74   */
75  @InterfaceAudience.Private
76  public class ReplicationPeersZKImpl extends ReplicationStateZKBase implements ReplicationPeers {
77  
78    // Map of peer clusters keyed by their id
79    private Map<String, ReplicationPeerZKImpl> peerClusters;
80    private final ReplicationQueuesClient queuesClient;
81  
82    private static final Log LOG = LogFactory.getLog(ReplicationPeersZKImpl.class);
83  
84    public ReplicationPeersZKImpl(final ZooKeeperWatcher zk, final Configuration conf,
85        final ReplicationQueuesClient queuesClient, Abortable abortable) {
86      super(zk, conf, abortable);
87      this.peerClusters = new ConcurrentHashMap<String, ReplicationPeerZKImpl>();
88      this.queuesClient = queuesClient;
89    }
90  
91    @Override
92    public void init() throws ReplicationException {
93      try {
94        if (ZKUtil.checkExists(this.zookeeper, this.peersZNode) < 0) {
95          ZKUtil.createWithParents(this.zookeeper, this.peersZNode);
96        }
97      } catch (KeeperException e) {
98        throw new ReplicationException("Could not initialize replication peers", e);
99      }
100     addExistingPeers();
101   }
102 
103   @Override
104   public void addPeer(String id, ReplicationPeerConfig peerConfig)
105       throws ReplicationException {
106     try {
107       if (peerExists(id)) {
108         throw new IllegalArgumentException("Cannot add a peer with id=" + id
109             + " because that id already exists.");
110       }
111 
112       if(id.contains("-") || id.equals(ReplicationQueuesZKImpl.RS_LOCK_ZNODE)) {
113         throw new IllegalArgumentException("Found invalid peer name:" + id);
114       }
115 
116       if (peerConfig.getClusterKey() != null) {
117         try {
118           ZKConfig.validateClusterKey(peerConfig.getClusterKey());
119         } catch (IOException ioe) {
120           throw new IllegalArgumentException(ioe.getMessage());
121         }
122       }
123 
124       checkQueuesDeleted(id);
125 
126       ZKUtil.createWithParents(this.zookeeper, this.peersZNode);
127 
128       List<ZKUtilOp> listOfOps = new ArrayList<ZKUtil.ZKUtilOp>();
129       ZKUtilOp op1 = ZKUtilOp.createAndFailSilent(getPeerNode(id),
130         ReplicationSerDeHelper.toByteArray(peerConfig));
131       // There is a race (if hbase.zookeeper.useMulti is false)
132       // b/w PeerWatcher and ReplicationZookeeper#add method to create the
133       // peer-state znode. This happens while adding a peer
134       // The peer state data is set as "ENABLED" by default.
135       ZKUtilOp op2 = ZKUtilOp.createAndFailSilent(getPeerStateNode(id), ENABLED_ZNODE_BYTES);
136       listOfOps.add(op1);
137       listOfOps.add(op2);
138       ZKUtil.multiOrSequential(this.zookeeper, listOfOps, false);
139       // A peer is enabled by default
140     } catch (KeeperException e) {
141       throw new ReplicationException("Could not add peer with id=" + id
142           + ", peerConfif=>" + peerConfig, e);
143     }
144   }
145 
146   @Override
147   public void removePeer(String id) throws ReplicationException {
148     try {
149       if (!peerExists(id)) {
150         throw new IllegalArgumentException("Cannot remove peer with id=" + id
151             + " because that id does not exist.");
152       }
153       ZKUtil.deleteNodeRecursively(this.zookeeper, ZKUtil.joinZNode(this.peersZNode, id));
154     } catch (KeeperException e) {
155       throw new ReplicationException("Could not remove peer with id=" + id, e);
156     }
157   }
158 
159   @Override
160   public void enablePeer(String id) throws ReplicationException {
161     changePeerState(id, ZooKeeperProtos.ReplicationState.State.ENABLED);
162     LOG.info("peer " + id + " is enabled");
163   }
164 
165   @Override
166   public void disablePeer(String id) throws ReplicationException {
167     changePeerState(id, ZooKeeperProtos.ReplicationState.State.DISABLED);
168     LOG.info("peer " + id + " is disabled");
169   }
170 
171   @Override
172   public Map<TableName, List<String>> getPeerTableCFsConfig(String id) throws ReplicationException {
173     try {
174       if (!peerExists(id)) {
175         throw new IllegalArgumentException("peer " + id + " doesn't exist");
176       }
177       try {
178         ReplicationPeerConfig rpc = getReplicationPeerConfig(id);
179         if (rpc == null) {
180           throw new ReplicationException("Unable to get tableCFs of the peer with id=" + id);
181         }
182         return rpc.getTableCFsMap();
183       } catch (Exception e) {
184         throw new ReplicationException(e);
185       }
186     } catch (KeeperException e) {
187       throw new ReplicationException("Unable to get tableCFs of the peer with id=" + id, e);
188     }
189   }
190 
191   @Override
192   public void setPeerTableCFsConfig(String id,
193       Map<TableName, ? extends Collection<String>> tableCFs) throws ReplicationException {
194     try {
195       if (!peerExists(id)) {
196         throw new IllegalArgumentException("Cannot set peer tableCFs because id=" + id
197             + " does not exist.");
198       }
199       ReplicationPeerConfig rpc = getReplicationPeerConfig(id);
200       if (rpc == null) {
201         throw new ReplicationException("Unable to get tableCFs of the peer with id=" + id);
202       }
203       rpc.setTableCFsMap(tableCFs);
204       ZKUtil.setData(this.zookeeper, getPeerNode(id),
205           ReplicationSerDeHelper.toByteArray(rpc));
206       LOG.info("Peer tableCFs with id= " + id + " is now "
207           + ReplicationSerDeHelper.convertToString(tableCFs));
208     } catch (KeeperException e) {
209       throw new ReplicationException("Unable to change tableCFs of the peer with id=" + id, e);
210     }
211   }
212 
213   @Override
214   public Map<TableName, List<String>> getTableCFs(String id) throws IllegalArgumentException {
215     ReplicationPeer replicationPeer = this.peerClusters.get(id);
216     if (replicationPeer == null) {
217       throw new IllegalArgumentException("Peer with id= " + id + " is not connected");
218     }
219     return replicationPeer.getTableCFs();
220   }
221 
222   @Override
223   public boolean getStatusOfPeer(String id) {
224     ReplicationPeer replicationPeer = this.peerClusters.get(id);
225     if (replicationPeer == null) {
226       throw new IllegalArgumentException("Peer with id= " + id + " is not connected");
227     }
228     return replicationPeer.getPeerState() == PeerState.ENABLED;
229   }
230 
231   @Override
232   public boolean getStatusOfPeerFromBackingStore(String id) throws ReplicationException {
233     try {
234       if (!peerExists(id)) {
235         throw new IllegalArgumentException("peer " + id + " doesn't exist");
236       }
237       String peerStateZNode = getPeerStateNode(id);
238       try {
239         return ReplicationPeerZKImpl.isStateEnabled(ZKUtil.getData(this.zookeeper, peerStateZNode));
240       } catch (KeeperException e) {
241         throw new ReplicationException(e);
242       } catch (DeserializationException e) {
243         throw new ReplicationException(e);
244       }
245     } catch (KeeperException e) {
246       throw new ReplicationException("Unable to get status of the peer with id=" + id +
247           " from backing store", e);
248     } catch (InterruptedException e) {
249       throw new ReplicationException(e);
250     }
251   }
252 
253   @Override
254   public Map<String, ReplicationPeerConfig> getAllPeerConfigs() {
255     Map<String, ReplicationPeerConfig> peers = new TreeMap<String, ReplicationPeerConfig>();
256     List<String> ids = null;
257     try {
258       ids = ZKUtil.listChildrenNoWatch(this.zookeeper, this.peersZNode);
259       for (String id : ids) {
260         ReplicationPeerConfig peerConfig = getReplicationPeerConfig(id);
261         if (peerConfig == null) {
262           LOG.warn("Failed to get replication peer configuration of clusterid=" + id
263             + " znode content, continuing.");
264           continue;
265         }
266         peers.put(id, peerConfig);
267       }
268     } catch (KeeperException e) {
269       this.abortable.abort("Cannot get the list of peers ", e);
270     } catch (ReplicationException e) {
271       this.abortable.abort("Cannot get the list of peers ", e);
272     }
273     return peers;
274   }
275 
276   @Override
277   public ReplicationPeer getPeer(String peerId) {
278     return peerClusters.get(peerId);
279   }
280 
281   @Override
282   public Set<String> getPeerIds() {
283     return peerClusters.keySet(); // this is not thread-safe
284   }
285 
286   /**
287    * Returns a ReplicationPeerConfig from the znode or null for the given peerId.
288    */
289   @Override
290   public ReplicationPeerConfig getReplicationPeerConfig(String peerId)
291       throws ReplicationException {
292     String znode = getPeerNode(peerId);
293     byte[] data = null;
294     try {
295       data = ZKUtil.getData(this.zookeeper, znode);
296     } catch (InterruptedException e) {
297       LOG.warn("Could not get configuration for peer because the thread " +
298           "was interrupted. peerId=" + peerId);
299       Thread.currentThread().interrupt();
300       return null;
301     } catch (KeeperException e) {
302       throw new ReplicationException("Error getting configuration for peer with id="
303           + peerId, e);
304     }
305     if (data == null) {
306       LOG.error("Could not get configuration for peer because it doesn't exist. peerId=" + peerId);
307       return null;
308     }
309 
310     try {
311       return ReplicationSerDeHelper.parsePeerFrom(data);
312     } catch (DeserializationException e) {
313       LOG.warn("Failed to parse cluster key from peerId=" + peerId
314           + ", specifically the content from the following znode: " + znode);
315       return null;
316     }
317   }
318 
319   @Override
320   public Pair<ReplicationPeerConfig, Configuration> getPeerConf(String peerId)
321       throws ReplicationException {
322     ReplicationPeerConfig peerConfig = getReplicationPeerConfig(peerId);
323 
324     if (peerConfig == null) {
325       return null;
326     }
327 
328     Configuration otherConf;
329     try {
330       otherConf = HBaseConfiguration.createClusterConf(this.conf, peerConfig.getClusterKey());
331     } catch (IOException e) {
332       LOG.error("Can't get peer configuration for peerId=" + peerId + " because:", e);
333       return null;
334     }
335 
336     if (!peerConfig.getConfiguration().isEmpty()) {
337       CompoundConfiguration compound = new CompoundConfiguration();
338       compound.add(otherConf);
339       compound.addStringMap(peerConfig.getConfiguration());
340       return new Pair<ReplicationPeerConfig, Configuration>(peerConfig, compound);
341     }
342 
343     return new Pair<ReplicationPeerConfig, Configuration>(peerConfig, otherConf);
344   }
345 
346   @Override
347   public void updatePeerConfig(String id, ReplicationPeerConfig newConfig)
348     throws ReplicationException {
349     ReplicationPeer peer = getPeer(id);
350     if (peer == null){
351       throw new ReplicationException("Could not find peer Id " + id);
352     }
353     ReplicationPeerConfig existingConfig = peer.getPeerConfig();
354     if (newConfig.getClusterKey() != null && !newConfig.getClusterKey().isEmpty() &&
355         !newConfig.getClusterKey().equals(existingConfig.getClusterKey())){
356       throw new ReplicationException("Changing the cluster key on an existing peer is not allowed."
357           + " Existing key '" + existingConfig.getClusterKey() + "' does not match new key '"
358           + newConfig.getClusterKey() +
359       "'");
360     }
361     String existingEndpointImpl = existingConfig.getReplicationEndpointImpl();
362     if (newConfig.getReplicationEndpointImpl() != null &&
363         !newConfig.getReplicationEndpointImpl().isEmpty() &&
364         !newConfig.getReplicationEndpointImpl().equals(existingEndpointImpl)){
365       throw new ReplicationException("Changing the replication endpoint implementation class " +
366           "on an existing peer is not allowed. Existing class '"
367           + existingConfig.getReplicationEndpointImpl()
368           + "' does not match new class '" + newConfig.getReplicationEndpointImpl() + "'");
369     }
370     //Update existingConfig's peer config and peer data with the new values, but don't touch config
371     // or data that weren't explicitly changed
372     existingConfig.getConfiguration().putAll(newConfig.getConfiguration());
373     existingConfig.getPeerData().putAll(newConfig.getPeerData());
374     existingConfig.setTableCFsMap(newConfig.getTableCFsMap());
375     existingConfig.setBandwidth(newConfig.getBandwidth());
376     try {
377       ZKUtil.setData(this.zookeeper, getPeerNode(id),
378           ReplicationSerDeHelper.toByteArray(existingConfig));
379     }
380     catch(KeeperException ke){
381       throw new ReplicationException("There was a problem trying to save changes to the " +
382           "replication peer " + id, ke);
383     }
384   }
385   /**
386    * List all registered peer clusters and set a watch on their znodes.
387    */
388   @Override
389   public List<String> getAllPeerIds() {
390     List<String> ids = null;
391     try {
392       ids = ZKUtil.listChildrenAndWatchThem(this.zookeeper, this.peersZNode);
393     } catch (KeeperException e) {
394       this.abortable.abort("Cannot get the list of peers ", e);
395     }
396     return ids;
397   }
398 
399   /**
400    * A private method used during initialization. This method attempts to add all registered
401    * peer clusters. This method does not set a watch on the peer cluster znodes.
402    */
403   private void addExistingPeers() throws ReplicationException {
404     List<String> znodes = null;
405     try {
406       znodes = ZKUtil.listChildrenNoWatch(this.zookeeper, this.peersZNode);
407     } catch (KeeperException e) {
408       throw new ReplicationException("Error getting the list of peer clusters.", e);
409     }
410     if (znodes != null) {
411       for (String z : znodes) {
412         createAndAddPeer(z);
413       }
414     }
415   }
416 
417   @Override
418   public boolean peerAdded(String peerId) throws ReplicationException {
419     return createAndAddPeer(peerId);
420   }
421 
422   @Override
423   public void peerRemoved(String peerId) {
424     ReplicationPeer rp = this.peerClusters.get(peerId);
425     if (rp != null) {
426       ((ConcurrentMap<String, ReplicationPeerZKImpl>) peerClusters).remove(peerId, rp);
427     }
428   }
429 
430   /**
431    * Attempt to connect to a new remote slave cluster.
432    * @param peerId a short that identifies the cluster
433    * @return true if a new connection was made, false if no new connection was made.
434    */
435   public boolean createAndAddPeer(String peerId) throws ReplicationException {
436     if (peerClusters == null) {
437       return false;
438     }
439     if (this.peerClusters.containsKey(peerId)) {
440       return false;
441     }
442 
443     ReplicationPeerZKImpl peer = null;
444     try {
445       peer = createPeer(peerId);
446     } catch (Exception e) {
447       throw new ReplicationException("Error adding peer with id=" + peerId, e);
448     }
449     if (peer == null) {
450       return false;
451     }
452     ReplicationPeerZKImpl previous =
453       ((ConcurrentMap<String, ReplicationPeerZKImpl>) peerClusters).putIfAbsent(peerId, peer);
454     if (previous == null) {
455       LOG.info("Added new peer cluster=" + peer.getPeerConfig().getClusterKey());
456     } else {
457       LOG.info("Peer already present, " + previous.getPeerConfig().getClusterKey() +
458         ", new cluster=" + peer.getPeerConfig().getClusterKey());
459     }
460     return true;
461   }
462 
463   /**
464    * Update the state znode of a peer cluster.
465    * @param id
466    * @param state
467    */
468   private void changePeerState(String id, ZooKeeperProtos.ReplicationState.State state)
469       throws ReplicationException {
470     try {
471       if (!peerExists(id)) {
472         throw new IllegalArgumentException("Cannot enable/disable peer because id=" + id
473             + " does not exist.");
474       }
475       String peerStateZNode = getPeerStateNode(id);
476       byte[] stateBytes =
477           (state == ZooKeeperProtos.ReplicationState.State.ENABLED) ? ENABLED_ZNODE_BYTES
478               : DISABLED_ZNODE_BYTES;
479       if (ZKUtil.checkExists(this.zookeeper, peerStateZNode) != -1) {
480         ZKUtil.setData(this.zookeeper, peerStateZNode, stateBytes);
481       } else {
482         ZKUtil.createAndWatch(this.zookeeper, peerStateZNode, stateBytes);
483       }
484       LOG.info("Peer with id= " + id + " is now " + state.name());
485     } catch (KeeperException e) {
486       throw new ReplicationException("Unable to change state of the peer with id=" + id, e);
487     }
488   }
489 
490   /**
491    * Helper method to connect to a peer
492    * @param peerId peer's identifier
493    * @return object representing the peer
494    * @throws ReplicationException
495    */
496   private ReplicationPeerZKImpl createPeer(String peerId) throws ReplicationException {
497     Pair<ReplicationPeerConfig, Configuration> pair = getPeerConf(peerId);
498     if (pair == null) {
499       return null;
500     }
501     Configuration peerConf = pair.getSecond();
502 
503     ReplicationPeerZKImpl peer = new ReplicationPeerZKImpl(zookeeper, peerConf, peerId,
504         pair.getFirst(), abortable);
505     try {
506       peer.startStateTracker(getPeerStateNode(peerId));
507     } catch (KeeperException e) {
508       throw new ReplicationException("Error starting the peer state tracker for peerId=" +
509           peerId, e);
510     }
511 
512     try {
513       peer.startPeerConfigTracker(this.zookeeper, this.getPeerNode(peerId));
514     }
515     catch(KeeperException e) {
516       throw new ReplicationException("Error starting the peer config tracker for peerId=" +
517         peerId, e);
518     }
519 
520     return peer;
521   }
522 
523   private void checkQueuesDeleted(String peerId) throws ReplicationException {
524     if (queuesClient == null) return;
525     try {
526       List<String> replicators = queuesClient.getListOfReplicators();
527       if (replicators == null || replicators.isEmpty()) {
528         return;
529       }
530       for (String replicator : replicators) {
531         List<String> queueIds = queuesClient.getAllQueues(replicator);
532         for (String queueId : queueIds) {
533           ReplicationQueueInfo queueInfo = new ReplicationQueueInfo(queueId);
534           if (queueInfo.getPeerId().equals(peerId)) {
535             throw new ReplicationException("undeleted queue for peerId: " + peerId
536                 + ", replicator: " + replicator + ", queueId: " + queueId);
537           }
538         }
539       }
540       // Check for hfile-refs queue
541       if (-1 != ZKUtil.checkExists(zookeeper, hfileRefsZNode)
542           && queuesClient.getAllPeersFromHFileRefsQueue().contains(peerId)) {
543         throw new ReplicationException("Undeleted queue for peerId: " + peerId
544             + ", found in hfile-refs node path " + hfileRefsZNode);
545       }
546     } catch (KeeperException e) {
547       throw new ReplicationException("Could not check queues deleted with id=" + peerId, e);
548     }
549   }
550 }