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 final 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  
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   private void readTableCFsZnode() {
162     String currentTableCFs = Bytes.toString(tableCFsTracker.getData(false));
163     this.tableCFs = parseTableCFsFromConfig(currentTableCFs);
164   }
165 
166   @Override
167   public PeerState getPeerState() {
168     return peerState;
169   }
170 
171   /**
172    * Get the identifier of this peer
173    * @return string representation of the id (short)
174    */
175   @Override
176   public String getId() {
177     return id;
178   }
179 
180   /**
181    * Get the peer config object
182    * @return the ReplicationPeerConfig for this peer
183    */
184   @Override
185   public ReplicationPeerConfig getPeerConfig() {
186     return peerConfig;
187   }
188 
189   /**
190    * Get the configuration object required to communicate with this peer
191    * @return configuration object
192    */
193   @Override
194   public Configuration getConfiguration() {
195     return conf;
196   }
197 
198   /**
199    * Get replicable (table, cf-list) map of this peer
200    * @return the replicable (table, cf-list) map
201    */
202   @Override
203   public Map<String, List<String>> getTableCFs() {
204     return this.tableCFs;
205   }
206 
207   @Override
208   public void abort(String why, Throwable e) {
209     LOG.fatal("The ReplicationPeer coresponding to peer " + peerConfig
210         + " was aborted for the following reason(s):" + why, e);
211   }
212 
213   @Override
214   public boolean isAborted() {
215     // Currently the replication peer is never "Aborted", we just log when the
216     // abort method is called.
217     return false;
218   }
219 
220   @Override
221   public void close() throws IOException {
222     // TODO: stop zkw?
223   }
224 
225   /**
226    * Parse the raw data from ZK to get a peer's state
227    * @param bytes raw ZK data
228    * @return True if the passed in <code>bytes</code> are those of a pb serialized ENABLED state.
229    * @throws DeserializationException
230    */
231   public static boolean isStateEnabled(final byte[] bytes) throws DeserializationException {
232     ZooKeeperProtos.ReplicationState.State state = parseStateFrom(bytes);
233     return ZooKeeperProtos.ReplicationState.State.ENABLED == state;
234   }
235 
236   /**
237    * @param bytes Content of a state znode.
238    * @return State parsed from the passed bytes.
239    * @throws DeserializationException
240    */
241   private static ZooKeeperProtos.ReplicationState.State parseStateFrom(final byte[] bytes)
242       throws DeserializationException {
243     ProtobufUtil.expectPBMagicPrefix(bytes);
244     int pblen = ProtobufUtil.lengthOfPBMagic();
245     ZooKeeperProtos.ReplicationState.Builder builder =
246         ZooKeeperProtos.ReplicationState.newBuilder();
247     ZooKeeperProtos.ReplicationState state;
248     try {
249       ProtobufUtil.mergeFrom(builder, bytes, pblen, bytes.length - pblen);
250       state = builder.build();
251       return state.getState();
252     } catch (IOException e) {
253       throw new DeserializationException(e);
254     }
255   }
256 
257   /**
258    * Utility method to ensure an ENABLED znode is in place; if not present, we create it.
259    * @param zookeeper
260    * @param path Path to znode to check
261    * @return True if we created the znode.
262    * @throws NodeExistsException
263    * @throws KeeperException
264    */
265   private static boolean ensurePeerEnabled(final ZooKeeperWatcher zookeeper, final String path)
266       throws NodeExistsException, KeeperException {
267     if (ZKUtil.checkExists(zookeeper, path) == -1) {
268       // There is a race b/w PeerWatcher and ReplicationZookeeper#add method to create the
269       // peer-state znode. This happens while adding a peer.
270       // The peer state data is set as "ENABLED" by default.
271       ZKUtil.createNodeIfNotExistsAndWatch(zookeeper, path,
272         ReplicationStateZKBase.ENABLED_ZNODE_BYTES);
273       return true;
274     }
275     return false;
276   }
277 
278   /**
279    * Tracker for state of this peer
280    */
281   public class PeerStateTracker extends ZooKeeperNodeTracker {
282 
283     public PeerStateTracker(String peerStateZNode, ZooKeeperWatcher watcher,
284         Abortable abortable) {
285       super(watcher, peerStateZNode, abortable);
286     }
287 
288     @Override
289     public synchronized void nodeDataChanged(String path) {
290       if (path.equals(node)) {
291         super.nodeDataChanged(path);
292         try {
293           readPeerStateZnode();
294         } catch (DeserializationException e) {
295           LOG.warn("Failed deserializing the content of " + path, e);
296         }
297       }
298     }
299   }
300 
301   /**
302    * Tracker for (table, cf-list) map of this peer
303    */
304   public class TableCFsTracker extends ZooKeeperNodeTracker {
305 
306     public TableCFsTracker(String tableCFsZNode, ZooKeeperWatcher watcher,
307         Abortable abortable) {
308       super(watcher, tableCFsZNode, abortable);
309     }
310 
311     @Override
312     public synchronized void nodeCreated(String path) {
313       if (path.equals(node)) {
314         super.nodeCreated(path);
315         readTableCFsZnode();
316       }
317     }
318 
319     @Override
320     public synchronized void nodeDataChanged(String path) {
321       if (path.equals(node)) {
322         super.nodeDataChanged(path);
323       }
324     }
325   }
326 }