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.zookeeper;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.hadoop.classification.InterfaceAudience;
24  import org.apache.hadoop.classification.InterfaceStability;
25  import org.apache.hadoop.hbase.Abortable;
26  import org.apache.zookeeper.KeeperException;
27  
28  /**
29   * Tracks the availability and value of a single ZooKeeper node.
30   *
31   * <p>Utilizes the {@link ZooKeeperListener} interface to get the necessary
32   * ZooKeeper events related to the node.
33   *
34   * <p>This is the base class used by trackers in both the Master and
35   * RegionServers.
36   */
37  @InterfaceAudience.Public
38  @InterfaceStability.Evolving
39  public abstract class ZooKeeperNodeTracker extends ZooKeeperListener {
40    
41    static final Log LOG = LogFactory.getLog(ZooKeeperNodeTracker.class);
42    /** Path of node being tracked */
43    protected final String node;
44  
45    /** Data of the node being tracked */
46    private byte [] data;
47  
48    /** Used to abort if a fatal error occurs */
49    protected final Abortable abortable;
50  
51    private boolean stopped = false;
52  
53    /**
54     * Constructs a new ZK node tracker.
55     *
56     * <p>After construction, use {@link #start} to kick off tracking.
57     *
58     * @param watcher
59     * @param node
60     * @param abortable
61     */
62    public ZooKeeperNodeTracker(ZooKeeperWatcher watcher, String node,
63        Abortable abortable) {
64      super(watcher);
65      this.node = node;
66      this.abortable = abortable;
67      this.data = null;
68    }
69  
70    /**
71     * Starts the tracking of the node in ZooKeeper.
72     *
73     * <p>Use {@link #blockUntilAvailable()} to block until the node is available
74     * or {@link #getData(boolean)} to get the data of the node if it is available.
75     */
76    public synchronized void start() {
77      this.watcher.registerListener(this);
78      try {
79        if(ZKUtil.watchAndCheckExists(watcher, node)) {
80          byte [] data = ZKUtil.getDataAndWatch(watcher, node);
81          if(data != null) {
82            this.data = data;
83          } else {
84            // It existed but now does not, try again to ensure a watch is set
85            LOG.debug("Try starting again because there is no data from " + node);
86            start();
87          }
88        }
89      } catch (KeeperException e) {
90        abortable.abort("Unexpected exception during initialization, aborting", e);
91      }
92    }
93  
94    public synchronized void stop() {
95      this.stopped = true;
96      notifyAll();
97    }
98  
99    /**
100    * Gets the data of the node, blocking until the node is available.
101    *
102    * @return data of the node
103    * @throws InterruptedException if the waiting thread is interrupted
104    */
105   public synchronized byte [] blockUntilAvailable()
106   throws InterruptedException {
107     return blockUntilAvailable(0, false);
108   }
109 
110   /**
111    * Gets the data of the node, blocking until the node is available or the
112    * specified timeout has elapsed.
113    *
114    * @param timeout maximum time to wait for the node data to be available,
115    * n milliseconds.  Pass 0 for no timeout.
116    * @return data of the node
117    * @throws InterruptedException if the waiting thread is interrupted
118    */
119   public synchronized byte [] blockUntilAvailable(long timeout, boolean refresh)
120   throws InterruptedException {
121     if (timeout < 0) throw new IllegalArgumentException();
122     boolean notimeout = timeout == 0;
123     long startTime = System.currentTimeMillis();
124     long remaining = timeout;
125     if (refresh) {
126       try {
127         // This does not create a watch if the node does not exists
128         this.data = ZKUtil.getDataAndWatch(watcher, node);
129       } catch(KeeperException e) {
130         // We use to abort here, but in some cases the abort is ignored (
131         //  (empty Abortable), so it's better to log...
132         LOG.warn("Unexpected exception handling blockUntilAvailable", e);
133         abortable.abort("Unexpected exception handling blockUntilAvailable", e);
134       }
135     }
136     boolean nodeExistsChecked = (!refresh ||data!=null);
137     while (!this.stopped && (notimeout || remaining > 0) && this.data == null) {
138       if (!nodeExistsChecked) {
139         try {
140           nodeExistsChecked = (ZKUtil.checkExists(watcher, node) != -1);
141         } catch (KeeperException e) {
142           LOG.warn(
143             "Got exception while trying to check existence in  ZooKeeper" +
144             " of the node: "+node+", retrying if timeout not reached",e );
145         }
146 
147         // It did not exists, and now it does.
148         if (nodeExistsChecked){
149           LOG.info("Node "+node+" now exists, resetting a watcher");
150           try {
151             // This does not create a watch if the node does not exists
152             this.data = ZKUtil.getDataAndWatch(watcher, node);
153           } catch (KeeperException e) {
154             LOG.warn("Unexpected exception handling blockUntilAvailable", e);
155             abortable.abort("Unexpected exception handling blockUntilAvailable", e);
156           }
157         }
158       }
159       // We expect a notification; but we wait with a
160       //  a timeout to lower the impact of a race condition if any
161       wait(100);
162       remaining = timeout - (System.currentTimeMillis() - startTime);
163     }
164     return this.data;
165   }
166 
167   /**
168    * Gets the data of the node.
169    *
170    * <p>If the node is currently available, the most up-to-date known version of
171    * the data is returned.  If the node is not currently available, null is
172    * returned.
173    * @param refresh whether to refresh the data by calling ZK directly.
174    * @return data of the node, null if unavailable
175    */
176   public synchronized byte [] getData(boolean refresh) {
177     if (refresh) {
178       try {
179         this.data = ZKUtil.getDataAndWatch(watcher, node);
180       } catch(KeeperException e) {
181         abortable.abort("Unexpected exception handling getData", e);
182       }
183     }
184     return this.data;
185   }
186 
187   public String getNode() {
188     return this.node;
189   }
190 
191   @Override
192   public synchronized void nodeCreated(String path) {
193     if (!path.equals(node)) return;
194     try {
195       byte [] data = ZKUtil.getDataAndWatch(watcher, node);
196       if (data != null) {
197         this.data = data;
198         notifyAll();
199       } else {
200         nodeDeleted(path);
201       }
202     } catch(KeeperException e) {
203       abortable.abort("Unexpected exception handling nodeCreated event", e);
204     }
205   }
206 
207   @Override
208   public synchronized void nodeDeleted(String path) {
209     if(path.equals(node)) {
210       try {
211         if(ZKUtil.watchAndCheckExists(watcher, node)) {
212           nodeCreated(path);
213         } else {
214           this.data = null;
215         }
216       } catch(KeeperException e) {
217         abortable.abort("Unexpected exception handling nodeDeleted event", e);
218       }
219     }
220   }
221 
222   @Override
223   public synchronized void nodeDataChanged(String path) {
224     if(path.equals(node)) {
225       nodeCreated(path);
226     }
227   }
228   
229   /**
230    * Checks if the baseznode set as per the property 'zookeeper.znode.parent'
231    * exists.
232    * @return true if baseznode exists.
233    *         false if doesnot exists.
234    */
235   public boolean checkIfBaseNodeAvailable() {
236     try {
237       if (ZKUtil.checkExists(watcher, watcher.baseZNode) == -1) {
238         return false;
239       }
240     } catch (KeeperException e) {
241       abortable
242           .abort(
243               "Exception while checking if basenode ("+watcher.baseZNode+
244                 ") exists in ZooKeeper.",
245               e);
246     }
247     return true;
248   }
249 }