View Javadoc

1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.zookeeper;
21  
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.hbase.master.AssignmentManager;
31  import org.apache.hadoop.hbase.util.Bytes;
32  import org.apache.zookeeper.KeeperException;
33  
34  /**
35   * Helper class for table state tracking for use by {@link AssignmentManager}.
36   * Reads, caches and sets state up in zookeeper.  If multiple read/write
37   * clients, will make for confusion.  Read-only clients other than
38   * AssignmentManager interested in learning table state can use the
39   * read-only utility methods {@link #isEnabledTable(ZooKeeperWatcher, String)}
40   * and {@link #isDisabledTable(ZooKeeperWatcher, String)}.
41   * 
42   * <p>To save on trips to the zookeeper ensemble, internally we cache table
43   * state.
44   */
45  public class ZKTable {
46    // A znode will exist under the table directory if it is in any of the
47    // following states: {@link TableState#ENABLING} , {@link TableState#DISABLING},
48    // or {@link TableState#DISABLED}.  If {@link TableState#ENABLED}, there will
49    // be no entry for a table in zk.  Thats how it currently works.
50  
51    private static final Log LOG = LogFactory.getLog(ZKTable.class);
52    private final ZooKeeperWatcher watcher;
53  
54    /**
55     * Cache of what we found in zookeeper so we don't have to go to zk ensemble
56     * for every query.  Synchronize access rather than use concurrent Map because
57     * synchronization needs to span query of zk.
58     */
59    private final Map<String, TableState> cache =
60      new HashMap<String, TableState>();
61  
62    // TODO: Make it so always a table znode. Put table schema here as well as table state.
63    // Have watcher on table znode so all are notified of state or schema change.
64    /**
65     * States a Table can be in.
66     * {@link TableState#ENABLED} is not used currently; its the absence of state
67     * in zookeeper that indicates an enabled table currently.
68     */
69    public static enum TableState {
70      ENABLED,
71      DISABLED,
72      DISABLING,
73      ENABLING
74    };
75  
76    public ZKTable(final ZooKeeperWatcher zkw) throws KeeperException {
77      super();
78      this.watcher = zkw;
79      populateTableStates();
80    }
81  
82    /**
83     * Gets a list of all the tables set as disabled in zookeeper.
84     * @param zkw
85     * @return list of disabled tables, empty list if none
86     * @throws KeeperException
87     */
88    private void populateTableStates()
89    throws KeeperException {
90      synchronized (this.cache) {
91        List<String> children =
92          ZKUtil.listChildrenNoWatch(this.watcher, this.watcher.tableZNode);
93        for (String child: children) {
94          TableState state = getTableState(this.watcher, child);
95          if (state != null) this.cache.put(child, state);
96        }
97      }
98    }
99  
100   /**
101    * @param zkw
102    * @param child
103    * @return Null or {@link TableState} found in znode.
104    * @throws KeeperException
105    */
106   private static TableState getTableState(final ZooKeeperWatcher zkw,
107       final String child)
108   throws KeeperException {
109     String znode = ZKUtil.joinZNode(zkw.tableZNode, child);
110     byte [] data = ZKUtil.getData(zkw, znode);
111     if (data == null || data.length <= 0) {
112       // Null if table is enabled.
113       return null;
114     }
115     String str = Bytes.toString(data);
116     try {
117       return TableState.valueOf(str);
118     } catch (IllegalArgumentException e) {
119       throw new IllegalArgumentException(str);
120     }
121   }
122 
123   /**
124    * Sets the specified table as DISABLED in zookeeper.  Fails silently if the
125    * table is already disabled in zookeeper.  Sets no watches.
126    * @param tableName
127    * @throws KeeperException unexpected zookeeper exception
128    */
129   public void setDisabledTable(String tableName)
130   throws KeeperException {
131     synchronized (this.cache) {
132       if (!isDisablingOrDisabledTable(tableName)) {
133         LOG.warn("Moving table " + tableName + " state to disabled but was " +
134           "not first in disabling state: " + this.cache.get(tableName));
135       }
136       setTableState(tableName, TableState.DISABLED);
137     }
138   }
139 
140   /**
141    * Sets the specified table as DISABLING in zookeeper.  Fails silently if the
142    * table is already disabled in zookeeper.  Sets no watches.
143    * @param tableName
144    * @throws KeeperException unexpected zookeeper exception
145    */
146   public void setDisablingTable(final String tableName)
147   throws KeeperException {
148     synchronized (this.cache) {
149       if (!isEnabledOrDisablingTable(tableName)) {
150         LOG.warn("Moving table " + tableName + " state to disabling but was " +
151           "not first in enabled state: " + this.cache.get(tableName));
152       }
153       setTableState(tableName, TableState.DISABLING);
154     }
155   }
156 
157   /**
158    * Sets the specified table as ENABLING in zookeeper.  Fails silently if the
159    * table is already disabled in zookeeper.  Sets no watches.
160    * @param tableName
161    * @throws KeeperException unexpected zookeeper exception
162    */
163   public void setEnablingTable(final String tableName)
164   throws KeeperException {
165     synchronized (this.cache) {
166       if (!isDisabledOrEnablingTable(tableName)) {
167         LOG.warn("Moving table " + tableName + " state to enabling but was " +
168           "not first in disabled state: " + this.cache.get(tableName));
169       }
170       setTableState(tableName, TableState.ENABLING);
171     }
172   }
173 
174   /**
175    * Sets the specified table as ENABLING in zookeeper atomically
176    * If the table is already in ENABLING state, no operation is performed
177    * @param tableName
178    * @return if the operation succeeds or not
179    * @throws KeeperException unexpected zookeeper exception
180    */
181   public boolean checkAndSetEnablingTable(final String tableName)
182     throws KeeperException {
183     synchronized (this.cache) {
184       if (isEnablingTable(tableName)) {
185         return false;
186       }
187       setTableState(tableName, TableState.ENABLING);
188       return true;
189     }
190   }
191 
192   /**
193    * Sets the specified table as ENABLING in zookeeper atomically
194    * If the table isn't in DISABLED state, no operation is performed
195    * @param tableName
196    * @return if the operation succeeds or not
197    * @throws KeeperException unexpected zookeeper exception
198    */
199   public boolean checkDisabledAndSetEnablingTable(final String tableName)
200     throws KeeperException {
201     synchronized (this.cache) {
202       if (!isDisabledTable(tableName)) {
203         return false;
204       }
205       setTableState(tableName, TableState.ENABLING);
206       return true;
207     }
208   }
209 
210   /**
211    * Sets the specified table as DISABLING in zookeeper atomically
212    * If the table isn't in ENABLED state, no operation is performed
213    * @param tableName
214    * @return if the operation succeeds or not
215    * @throws KeeperException unexpected zookeeper exception
216    */
217   public boolean checkEnabledAndSetDisablingTable(final String tableName)
218     throws KeeperException {
219     synchronized (this.cache) {
220       if (!isEnabledTable(tableName)) {
221         return false;
222       }
223       setTableState(tableName, TableState.DISABLING);
224       return true;
225     }
226   }
227 
228   private void setTableState(final String tableName, final TableState state)
229   throws KeeperException {
230     String znode = ZKUtil.joinZNode(this.watcher.tableZNode, tableName);
231     if (ZKUtil.checkExists(this.watcher, znode) == -1) {
232       ZKUtil.createAndFailSilent(this.watcher, znode);
233     }
234     synchronized (this.cache) {
235       ZKUtil.setData(this.watcher, znode, Bytes.toBytes(state.toString()));
236       this.cache.put(tableName, state);
237     }
238   }
239 
240   public boolean isDisabledTable(final String tableName) {
241     return isTableState(tableName, TableState.DISABLED);
242   }
243 
244   /**
245    * Go to zookeeper and see if state of table is {@link TableState#DISABLED}.
246    * This method does not use cache as {@link #isDisabledTable(String)} does.
247    * This method is for clients other than {@link AssignmentManager}
248    * @param zkw
249    * @param tableName
250    * @return True if table is enabled.
251    * @throws KeeperException
252    */
253   public static boolean isDisabledTable(final ZooKeeperWatcher zkw,
254       final String tableName)
255   throws KeeperException {
256     TableState state = getTableState(zkw, tableName);
257     return isTableState(TableState.DISABLED, state);
258   }
259 
260   public boolean isDisablingTable(final String tableName) {
261     return isTableState(tableName, TableState.DISABLING);
262   }
263 
264   public boolean isEnablingTable(final String tableName) {
265     return isTableState(tableName, TableState.ENABLING);
266   }
267 
268   public boolean isEnabledTable(String tableName) {
269     synchronized (this.cache) {
270       // No entry in cache means enabled table.
271       return !this.cache.containsKey(tableName);
272     }
273   }
274 
275   /**
276    * Go to zookeeper and see if state of table is {@link TableState#ENABLED}.
277    * This method does not use cache as {@link #isEnabledTable(String)} does.
278    * This method is for clients other than {@link AssignmentManager}
279    * @param zkw
280    * @param tableName
281    * @return True if table is enabled.
282    * @throws KeeperException
283    */
284   public static boolean isEnabledTable(final ZooKeeperWatcher zkw,
285       final String tableName)
286   throws KeeperException {
287     return getTableState(zkw, tableName) == null;
288   }
289 
290   public boolean isDisablingOrDisabledTable(final String tableName) {
291     synchronized (this.cache) {
292       return isDisablingTable(tableName) || isDisabledTable(tableName);
293     }
294   }
295 
296   /**
297    * Go to zookeeper and see if state of table is {@link TableState#DISABLING}
298    * of {@link TableState#DISABLED}.
299    * This method does not use cache as {@link #isEnabledTable(String)} does.
300    * This method is for clients other than {@link AssignmentManager}.
301    * @param zkw
302    * @param tableName
303    * @return True if table is enabled.
304    * @throws KeeperException
305    */
306   public static boolean isDisablingOrDisabledTable(final ZooKeeperWatcher zkw,
307       final String tableName)
308   throws KeeperException {
309     TableState state = getTableState(zkw, tableName);
310     return isTableState(TableState.DISABLING, state) ||
311       isTableState(TableState.DISABLED, state);
312   }
313 
314   public boolean isEnabledOrDisablingTable(final String tableName) {
315     synchronized (this.cache) {
316       return isEnabledTable(tableName) || isDisablingTable(tableName);
317     }
318   }
319 
320   public boolean isDisabledOrEnablingTable(final String tableName) {
321     synchronized (this.cache) {
322       return isDisabledTable(tableName) || isEnablingTable(tableName);
323     }
324   }
325 
326   private boolean isTableState(final String tableName, final TableState state) {
327     synchronized (this.cache) {
328       TableState currentState = this.cache.get(tableName);
329       return isTableState(currentState, state);
330     }
331   }
332 
333   private static boolean isTableState(final TableState expectedState,
334       final TableState currentState) {
335     return currentState != null && currentState.equals(expectedState);
336   }
337 
338   /**
339    * Enables the table in zookeeper.  Fails silently if the
340    * table is not currently disabled in zookeeper.  Sets no watches.
341    * @param tableName
342    * @throws KeeperException unexpected zookeeper exception
343    */
344   public void setEnabledTable(final String tableName)
345   throws KeeperException {
346     synchronized (this.cache) {
347       if (this.cache.remove(tableName) == null) {
348         LOG.warn("Moving table " + tableName + " state to enabled but was " +
349           "already enabled");
350       }
351       ZKUtil.deleteNodeFailSilent(this.watcher,
352         ZKUtil.joinZNode(this.watcher.tableZNode, tableName));
353     }
354   }
355 
356   /**
357    * Gets a list of all the tables set as disabled in zookeeper.
358    * @return Set of disabled tables, empty Set if none
359    */
360   public Set<String> getDisabledTables() {
361     Set<String> disabledTables = new HashSet<String>();
362     synchronized (this.cache) {
363       Set<String> tables = this.cache.keySet();
364       for (String table: tables) {
365         if (isDisabledTable(table)) disabledTables.add(table);
366       }
367     }
368     return disabledTables;
369   }
370 
371   /**
372    * Gets a list of all the tables set as disabled in zookeeper.
373    * @return Set of disabled tables, empty Set if none
374    * @throws KeeperException 
375    */
376   public static Set<String> getDisabledTables(ZooKeeperWatcher zkw)
377   throws KeeperException {
378     Set<String> disabledTables = new HashSet<String>();
379     List<String> children =
380       ZKUtil.listChildrenNoWatch(zkw, zkw.tableZNode);
381     for (String child: children) {
382       TableState state = getTableState(zkw, child);
383       if (state == TableState.DISABLED) disabledTables.add(child);
384     }
385     return disabledTables;
386   }
387 
388   /**
389    * Gets a list of all the tables set as disabled in zookeeper.
390    * @return Set of disabled tables, empty Set if none
391    * @throws KeeperException 
392    */
393   public static Set<String> getDisabledOrDisablingTables(ZooKeeperWatcher zkw)
394   throws KeeperException {
395     Set<String> disabledTables = new HashSet<String>();
396     List<String> children =
397       ZKUtil.listChildrenNoWatch(zkw, zkw.tableZNode);
398     for (String child: children) {
399       TableState state = getTableState(zkw, child);
400       if (state == TableState.DISABLED || state == TableState.DISABLING)
401         disabledTables.add(child);
402     }
403     return disabledTables;
404   }
405 }