View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.zookeeper;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.List;
24  import org.apache.hadoop.hbase.classification.InterfaceAudience;
25  import org.apache.hadoop.hbase.Abortable;
26  import org.apache.hadoop.hbase.HConstants;
27  import org.apache.hadoop.hbase.ServerName;
28  import org.apache.hadoop.hbase.exceptions.DeserializationException;
29  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
30  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
31  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
32  import org.apache.zookeeper.KeeperException;
33  import org.apache.zookeeper.data.Stat;
34  
35  import java.io.IOException;
36  import java.io.InterruptedIOException;
37  import com.google.protobuf.InvalidProtocolBufferException;
38  
39  /**
40   * Manages the location of the current active Master for the RegionServer.
41   * <p>
42   * Listens for ZooKeeper events related to the master address. The node
43   * <code>/master</code> will contain the address of the current master.
44   * This listener is interested in
45   * <code>NodeDeleted</code> and <code>NodeCreated</code> events on
46   * <code>/master</code>.
47   * <p>
48   * Utilizes {@link ZooKeeperNodeTracker} for zk interactions.
49   * <p>
50   * You can get the current master via {@link #getMasterAddress()} or via
51   * {@link #getMasterAddress(ZooKeeperWatcher)} if you do not have a running
52   * instance of this Tracker in your context.
53   * <p>
54   * This class also includes utility for interacting with the master znode, for
55   * writing and reading the znode content.
56   */
57  @InterfaceAudience.Private
58  public class MasterAddressTracker extends ZooKeeperNodeTracker {
59    /**
60     * Construct a master address listener with the specified
61     * <code>zookeeper</code> reference.
62     * <p>
63     * This constructor does not trigger any actions, you must call methods
64     * explicitly.  Normally you will just want to execute {@link #start()} to
65     * begin tracking of the master address.
66     *
67     * @param watcher zk reference and watcher
68     * @param abortable abortable in case of fatal error
69     */
70    public MasterAddressTracker(ZooKeeperWatcher watcher, Abortable abortable) {
71      super(watcher, watcher.getMasterAddressZNode(), abortable);
72    }
73  
74    /**
75     * @param watcher ZooKeeperWatcher instance to use for querying ZK.
76     * @return current list of backup masters.
77     */
78    public static List<ServerName> getBackupMastersAndRenewWatch(
79        ZooKeeperWatcher watcher) {
80      // Build Set of backup masters from ZK nodes
81      List<String> backupMasterStrings;
82      try {
83        backupMasterStrings = ZKUtil.listChildrenAndWatchForNewChildren(
84            watcher, watcher.backupMasterAddressesZNode);
85      } catch (KeeperException e) {
86        LOG.warn(watcher.prefix("Unable to list backup servers"), e);
87        backupMasterStrings = null;
88      }
89  
90      List<ServerName> backupMasters = new ArrayList<>();
91      if (backupMasterStrings != null && !backupMasterStrings.isEmpty()) {
92        for (String s: backupMasterStrings) {
93          try {
94            byte [] bytes;
95            try {
96              bytes = ZKUtil.getData(watcher, ZKUtil.joinZNode(
97                 watcher.backupMasterAddressesZNode, s));
98            } catch (InterruptedException e) {
99              throw new InterruptedIOException("Thread interrupted.");
100           }
101           if (bytes != null) {
102             ServerName sn;
103             try {
104               sn = ServerName.parseFrom(bytes);
105             } catch (DeserializationException e) {
106               LOG.warn("Failed parse, skipping registering backup server", e);
107               continue;
108             }
109             backupMasters.add(sn);
110           }
111         } catch (KeeperException | InterruptedIOException e) {
112           LOG.warn(watcher.prefix("Unable to get information about " +
113               "backup servers"), e);
114         }
115       }
116       Collections.sort(backupMasters, new Comparator<ServerName>() {
117         @Override
118         public int compare(ServerName s1, ServerName s2) {
119           return s1.getServerName().compareTo(s2.getServerName());
120         }});
121     }
122     return backupMasters;
123   }
124 
125   /**
126    * Get the address of the current master if one is available.  Returns null
127    * if no current master.
128    * @return Server name or null if timed out.
129    */
130   public ServerName getMasterAddress() {
131     return getMasterAddress(false);
132   }
133 
134   /**
135    * Get the info port of the current master of one is available.
136    * Return 0 if no current master or zookeeper is unavailable
137    * @return info port or 0 if timed out
138    */
139   public int getMasterInfoPort() {
140     try {
141       final ZooKeeperProtos.Master master = parse(this.getData(false));
142       if (master == null) {
143         return 0;
144       }
145       return master.getInfoPort();
146     } catch (DeserializationException e) {
147       LOG.warn("Failed parse master zk node data", e);
148       return 0;
149     }
150   }
151   /**
152    * Get the info port of the backup master if it is available.
153    * Return 0 if no backup master or zookeeper is unavailable
154    * @param sn server name of backup master
155    * @return info port or 0 if timed out or exceptions
156    */
157   public int getBackupMasterInfoPort(final ServerName sn) {
158     String backupZNode = ZKUtil.joinZNode(watcher.backupMasterAddressesZNode, sn.toString());
159     try {
160       byte[] data = ZKUtil.getData(watcher, backupZNode);
161       final ZooKeeperProtos.Master backup = parse(data);
162       if (backup == null) {
163         return 0;
164       }
165       return backup.getInfoPort();
166     } catch (Exception e) {
167       LOG.warn("Failed to get backup master: " + sn + "'s info port.", e);
168       return 0;
169     }
170   }
171 
172   /**
173    * Get the address of the current master if one is available.  Returns null
174    * if no current master. If refresh is set, try to load the data from ZK again,
175    * otherwise, cached data will be used.
176    *
177    * @param refresh whether to refresh the data by calling ZK directly.
178    * @return Server name or null if timed out.
179    */
180   public ServerName getMasterAddress(final boolean refresh) {
181     try {
182       return ServerName.parseFrom(super.getData(refresh));
183     } catch (DeserializationException e) {
184       LOG.warn("Failed parse", e);
185       return null;
186     }
187   }
188 
189   /**
190    * Get master address.
191    * Use this instead of {@link #getMasterAddress()} if you do not have an
192    * instance of this tracker in your context.
193    * @param zkw ZooKeeperWatcher to use
194    * @return ServerName stored in the the master address znode or null if no
195    * znode present.
196    * @throws KeeperException
197    * @throws IOException
198    */
199   public static ServerName getMasterAddress(final ZooKeeperWatcher zkw)
200   throws KeeperException, IOException {
201     byte [] data;
202     try {
203       data = ZKUtil.getData(zkw, zkw.getMasterAddressZNode());
204     } catch (InterruptedException e) {
205       throw new InterruptedIOException();
206     }
207     // TODO javadoc claims we return null in this case. :/
208     if (data == null){
209       throw new IOException("Can't get master address from ZooKeeper; znode data == null");
210     }
211     try {
212       return ServerName.parseFrom(data);
213     } catch (DeserializationException e) {
214       KeeperException ke = new KeeperException.DataInconsistencyException();
215       ke.initCause(e);
216       throw ke;
217     }
218   }
219 
220   /**
221    * Get master info port.
222    * Use this instead of {@link #getMasterInfoPort()} if you do not have an
223    * instance of this tracker in your context.
224    * @param zkw ZooKeeperWatcher to use
225    * @return master info port in the the master address znode or null if no
226    * znode present.
227    * // TODO can't return null for 'int' return type. non-static verison returns 0
228    * @throws KeeperException
229    * @throws IOException
230    */
231   public static int getMasterInfoPort(final ZooKeeperWatcher zkw) throws KeeperException,
232       IOException {
233     byte[] data;
234     try {
235       data = ZKUtil.getData(zkw, zkw.getMasterAddressZNode());
236     } catch (InterruptedException e) {
237       throw new InterruptedIOException();
238     }
239     // TODO javadoc claims we return null in this case. :/
240     if (data == null) {
241       throw new IOException("Can't get master address from ZooKeeper; znode data == null");
242     }
243     try {
244       return parse(data).getInfoPort();
245     } catch (DeserializationException e) {
246       KeeperException ke = new KeeperException.DataInconsistencyException();
247       ke.initCause(e);
248       throw ke;
249     }
250   }
251 
252   /**
253    * Set master address into the <code>master</code> znode or into the backup
254    * subdirectory of backup masters; switch off the passed in <code>znode</code>
255    * path.
256    * @param zkw The ZooKeeperWatcher to use.
257    * @param znode Where to create the znode; could be at the top level or it
258    * could be under backup masters
259    * @param master ServerName of the current master must not be null.
260    * @return true if node created, false if not; a watch is set in both cases
261    * @throws KeeperException
262    */
263   public static boolean setMasterAddress(final ZooKeeperWatcher zkw,
264       final String znode, final ServerName master, int infoPort)
265   throws KeeperException {
266     return ZKUtil.createEphemeralNodeAndWatch(zkw, znode, toByteArray(master, infoPort));
267   }
268 
269   /**
270    * Check if there is a master available.
271    * @return true if there is a master set, false if not.
272    */
273   public boolean hasMaster() {
274     return super.getData(false) != null;
275   }
276 
277   /**
278    * @param sn must not be null
279    * @return Content of the master znode as a serialized pb with the pb
280    * magic as prefix.
281    */
282   static byte[] toByteArray(final ServerName sn, int infoPort) {
283     ZooKeeperProtos.Master.Builder mbuilder = ZooKeeperProtos.Master.newBuilder();
284     HBaseProtos.ServerName.Builder snbuilder = HBaseProtos.ServerName.newBuilder();
285     snbuilder.setHostName(sn.getHostname());
286     snbuilder.setPort(sn.getPort());
287     snbuilder.setStartCode(sn.getStartcode());
288     mbuilder.setMaster(snbuilder.build());
289     mbuilder.setRpcVersion(HConstants.RPC_CURRENT_VERSION);
290     mbuilder.setInfoPort(infoPort);
291     return ProtobufUtil.prependPBMagic(mbuilder.build().toByteArray());
292   }
293 
294   /**
295    * @param data zookeeper data. may be null
296    * @return pb object of master, null if no active master
297    * @throws DeserializationException
298    */
299   public static ZooKeeperProtos.Master parse(byte[] data) throws DeserializationException {
300     if (data == null) {
301       return null;
302     }
303     int prefixLen = ProtobufUtil.lengthOfPBMagic();
304     try {
305       return ZooKeeperProtos.Master.PARSER.parseFrom(data, prefixLen, data.length - prefixLen);
306     } catch (InvalidProtocolBufferException e) {
307       throw new DeserializationException(e);
308     }
309   }
310   /**
311    * delete the master znode if its content is same as the parameter
312    * @param zkw must not be null
313    * @param content must not be null
314    */
315   public static boolean deleteIfEquals(ZooKeeperWatcher zkw, final String content) {
316     if (content == null){
317       throw new IllegalArgumentException("Content must not be null");
318     }
319 
320     try {
321       Stat stat = new Stat();
322       byte[] data = ZKUtil.getDataNoWatch(zkw, zkw.getMasterAddressZNode(), stat);
323       ServerName sn = ServerName.parseFrom(data);
324       if (sn != null && content.equals(sn.toString())) {
325         return (ZKUtil.deleteNode(zkw, zkw.getMasterAddressZNode(), stat.getVersion()));
326       }
327     } catch (KeeperException e) {
328       LOG.warn("Can't get or delete the master znode", e);
329     } catch (DeserializationException e) {
330       LOG.warn("Can't get or delete the master znode", e);
331     }
332 
333     return false;
334   }
335 }