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.Closeable;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.Abortable;
33  import org.apache.hadoop.hbase.exceptions.DeserializationException;
34  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
35  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
36  import org.apache.hadoop.hbase.util.Bytes;
37  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
38  import org.apache.hadoop.hbase.zookeeper.ZooKeeperNodeTracker;
39  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
40  import org.apache.zookeeper.KeeperException;
41  import org.apache.zookeeper.KeeperException.NodeExistsException;
42  
43  @InterfaceAudience.Private
44  public class ReplicationPeerZKImpl implements ReplicationPeer, Abortable, Closeable {
45    private static final Log LOG = LogFactory.getLog(ReplicationPeerZKImpl.class);
46  
47    private ReplicationPeerConfig peerConfig;
48    private final String id;
49    private volatile PeerState peerState;
50    private volatile Map<String, List<String>> tableCFs = new HashMap<String, List<String>>();
51    private final Configuration conf;
52  
53    private PeerStateTracker peerStateTracker;
54    private TableCFsTracker tableCFsTracker;
55    private PeerConfigTracker peerConfigTracker;
56    /**
57     * Constructor that takes all the objects required to communicate with the
58     * specified peer, except for the region server addresses.
59     * @param conf configuration object to this peer
60     * @param id string representation of this peer's identifier
61     * @param peerConfig configuration for the replication peer
62     */
63    public ReplicationPeerZKImpl(Configuration conf, String id, ReplicationPeerConfig peerConfig)
64        throws ReplicationException {
65      this.conf = conf;
66      this.peerConfig = peerConfig;
67      this.id = id;
68    }
69  
70    /**
71     * start a state tracker to check whether this peer is enabled or not
72     *
73     * @param zookeeper zk watcher for the local cluster
74     * @param peerStateNode path to zk node which stores peer state
75     * @throws KeeperException
76     */
77    public void startStateTracker(ZooKeeperWatcher zookeeper, String peerStateNode)
78        throws KeeperException {
79      ensurePeerEnabled(zookeeper, peerStateNode);
80      this.peerStateTracker = new PeerStateTracker(peerStateNode, zookeeper, this);
81      this.peerStateTracker.start();
82      try {
83        this.readPeerStateZnode();
84      } catch (DeserializationException e) {
85        throw ZKUtil.convert(e);
86      }
87    }
88  
89    private void readPeerStateZnode() throws DeserializationException {
90      this.peerState =
91          isStateEnabled(this.peerStateTracker.getData(false))
92            ? PeerState.ENABLED
93            : PeerState.DISABLED;
94    }
95  
96    /**
97     * start a table-cfs tracker to listen the (table, cf-list) map change
98     *
99     * @param zookeeper zk watcher for the local cluster
100    * @param tableCFsNode path to zk node which stores table-cfs
101    * @throws KeeperException
102    */
103   public void startTableCFsTracker(ZooKeeperWatcher zookeeper, String tableCFsNode)
104     throws KeeperException {
105     this.tableCFsTracker = new TableCFsTracker(tableCFsNode, zookeeper,
106         this);
107     this.tableCFsTracker.start();
108     this.readTableCFsZnode();
109   }
110 
111   static Map<String, List<String>> parseTableCFsFromConfig(String tableCFsConfig) {
112     if (tableCFsConfig == null || tableCFsConfig.trim().length() == 0) {
113       return null;
114     }
115 
116     Map<String, List<String>> tableCFsMap = null;
117     // TODO: This should be a PB object rather than a String to be parsed!! See HBASE-11393
118     // parse out (table, cf-list) pairs from tableCFsConfig
119     // format: "table1:cf1,cf2;table2:cfA,cfB"
120     String[] tables = tableCFsConfig.split(";");
121     for (String tab : tables) {
122       // 1 ignore empty table config
123       tab = tab.trim();
124       if (tab.length() == 0) {
125         continue;
126       }
127       // 2 split to "table" and "cf1,cf2"
128       //   for each table: "table:cf1,cf2" or "table"
129       String[] pair = tab.split(":");
130       String tabName = pair[0].trim();
131       if (pair.length > 2 || tabName.length() == 0) {
132         LOG.error("ignore invalid tableCFs setting: " + tab);
133         continue;
134       }
135 
136       // 3 parse "cf1,cf2" part to List<cf>
137       List<String> cfs = null;
138       if (pair.length == 2) {
139         String[] cfsList = pair[1].split(",");
140         for (String cf : cfsList) {
141           String cfName = cf.trim();
142           if (cfName.length() > 0) {
143             if (cfs == null) {
144               cfs = new ArrayList<String>();
145             }
146             cfs.add(cfName);
147           }
148         }
149       }
150 
151       // 4 put <table, List<cf>> to map
152       if (tableCFsMap == null) {
153         tableCFsMap = new HashMap<String, List<String>>();
154       }
155       tableCFsMap.put(tabName, cfs);
156     }
157 
158     return tableCFsMap;
159   }
160   /**
161    * start a table-cfs tracker to listen the (table, cf-list) map change
162    * @param zookeeper
163    * @param peerConfigNode path to zk node which stores table-cfs
164    * @throws KeeperException
165    */
166   public void startPeerConfigTracker(ZooKeeperWatcher zookeeper, String peerConfigNode)
167       throws KeeperException {
168     this.peerConfigTracker = new PeerConfigTracker(peerConfigNode, zookeeper,
169         this);
170     this.peerConfigTracker.start();
171     this.readPeerConfig();
172   }
173 
174   private void readTableCFsZnode() {
175     String currentTableCFs = Bytes.toString(tableCFsTracker.getData(false));
176     this.tableCFs = parseTableCFsFromConfig(currentTableCFs);
177   }
178 
179   private ReplicationPeerConfig readPeerConfig() {
180     try {
181       byte[] data = peerConfigTracker.getData(false);
182       if (data != null) {
183         this.peerConfig = ReplicationSerDeHelper.parsePeerFrom(data);
184       }
185     } catch (DeserializationException e) {
186       LOG.error("", e);
187     }
188     return this.peerConfig;
189   }
190   @Override
191   public PeerState getPeerState() {
192     return peerState;
193   }
194 
195   /**
196    * Get the identifier of this peer
197    * @return string representation of the id (short)
198    */
199   @Override
200   public String getId() {
201     return id;
202   }
203 
204   /**
205    * Get the peer config object
206    * @return the ReplicationPeerConfig for this peer
207    */
208   @Override
209   public ReplicationPeerConfig getPeerConfig() {
210     return peerConfig;
211   }
212 
213   /**
214    * Get the configuration object required to communicate with this peer
215    * @return configuration object
216    */
217   @Override
218   public Configuration getConfiguration() {
219     return conf;
220   }
221 
222   /**
223    * Get replicable (table, cf-list) map of this peer
224    * @return the replicable (table, cf-list) map
225    */
226   @Override
227   public Map<String, List<String>> getTableCFs() {
228     return this.tableCFs;
229   }
230 
231   @Override
232   public void trackPeerConfigChanges(ReplicationPeerConfigListener listener) {
233     if (this.peerConfigTracker != null){
234       this.peerConfigTracker.setListener(listener);
235     }
236   }
237 
238   @Override
239   public void abort(String why, Throwable e) {
240     LOG.fatal("The ReplicationPeer coresponding to peer " + peerConfig
241         + " was aborted for the following reason(s):" + why, e);
242   }
243 
244   @Override
245   public boolean isAborted() {
246     // Currently the replication peer is never "Aborted", we just log when the
247     // abort method is called.
248     return false;
249   }
250 
251   @Override
252   public void close() throws IOException {
253     // TODO: stop zkw?
254   }
255 
256   /**
257    * Parse the raw data from ZK to get a peer's state
258    * @param bytes raw ZK data
259    * @return True if the passed in <code>bytes</code> are those of a pb serialized ENABLED state.
260    * @throws DeserializationException
261    */
262   public static boolean isStateEnabled(final byte[] bytes) throws DeserializationException {
263     ZooKeeperProtos.ReplicationState.State state = parseStateFrom(bytes);
264     return ZooKeeperProtos.ReplicationState.State.ENABLED == state;
265   }
266 
267   /**
268    * @param bytes Content of a state znode.
269    * @return State parsed from the passed bytes.
270    * @throws DeserializationException
271    */
272   private static ZooKeeperProtos.ReplicationState.State parseStateFrom(final byte[] bytes)
273       throws DeserializationException {
274     ProtobufUtil.expectPBMagicPrefix(bytes);
275     int pblen = ProtobufUtil.lengthOfPBMagic();
276     ZooKeeperProtos.ReplicationState.Builder builder =
277         ZooKeeperProtos.ReplicationState.newBuilder();
278     ZooKeeperProtos.ReplicationState state;
279     try {
280       ProtobufUtil.mergeFrom(builder, bytes, pblen, bytes.length - pblen);
281       state = builder.build();
282       return state.getState();
283     } catch (IOException e) {
284       throw new DeserializationException(e);
285     }
286   }
287 
288   /**
289    * Utility method to ensure an ENABLED znode is in place; if not present, we create it.
290    * @param zookeeper
291    * @param path Path to znode to check
292    * @return True if we created the znode.
293    * @throws NodeExistsException
294    * @throws KeeperException
295    */
296   private static boolean ensurePeerEnabled(final ZooKeeperWatcher zookeeper, final String path)
297       throws NodeExistsException, KeeperException {
298     if (ZKUtil.checkExists(zookeeper, path) == -1) {
299       // There is a race b/w PeerWatcher and ReplicationZookeeper#add method to create the
300       // peer-state znode. This happens while adding a peer.
301       // The peer state data is set as "ENABLED" by default.
302       ZKUtil.createNodeIfNotExistsAndWatch(zookeeper, path,
303         ReplicationStateZKBase.ENABLED_ZNODE_BYTES);
304       return true;
305     }
306     return false;
307   }
308 
309   /**
310    * Tracker for state of this peer
311    */
312   public class PeerStateTracker extends ZooKeeperNodeTracker {
313 
314     public PeerStateTracker(String peerStateZNode, ZooKeeperWatcher watcher,
315         Abortable abortable) {
316       super(watcher, peerStateZNode, abortable);
317     }
318 
319     @Override
320     public synchronized void nodeDataChanged(String path) {
321       if (path.equals(node)) {
322         super.nodeDataChanged(path);
323         try {
324           readPeerStateZnode();
325         } catch (DeserializationException e) {
326           LOG.warn("Failed deserializing the content of " + path, e);
327         }
328       }
329     }
330   }
331 
332   /**
333    * Tracker for (table, cf-list) map of this peer
334    */
335   public class TableCFsTracker extends ZooKeeperNodeTracker {
336 
337     public TableCFsTracker(String tableCFsZNode, ZooKeeperWatcher watcher,
338         Abortable abortable) {
339       super(watcher, tableCFsZNode, abortable);
340     }
341 
342     @Override
343     public synchronized void nodeCreated(String path) {
344       if (path.equals(node)) {
345         super.nodeCreated(path);
346         readTableCFsZnode();
347       }
348     }
349 
350     @Override
351     public synchronized void nodeDataChanged(String path) {
352       if (path.equals(node)) {
353         super.nodeDataChanged(path);
354       }
355     }
356   }
357 
358   /**
359    * Tracker for PeerConfigNode of this peer
360    */
361   public class PeerConfigTracker extends ZooKeeperNodeTracker {
362 
363     private ReplicationPeerConfigListener listener;
364 
365     public PeerConfigTracker(String peerConfigNode, ZooKeeperWatcher watcher,
366                              Abortable abortable) {
367       super(watcher, peerConfigNode, abortable);
368     }
369 
370     public synchronized void setListener(ReplicationPeerConfigListener listener){
371       this.listener = listener;
372     }
373 
374     @Override
375     public synchronized void nodeCreated(String path) {
376       if (path.equals(node)) {
377         super.nodeCreated(path);
378         ReplicationPeerConfig config = readPeerConfig();
379         if (listener != null){
380           listener.peerConfigUpdated(config);
381         }
382       }
383     }
384 
385     @Override
386     public synchronized void nodeDataChanged(String path) {
387       //superclass calls nodeCreated
388       if (path.equals(node)) {
389         super.nodeDataChanged(path);
390       }
391     }
392   }
393 }