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