001/**
002 * Copyright 2010 The Apache Software Foundation
003 *
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *     http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020package org.apache.hadoop.hbase.zookeeper;
021
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.apache.hadoop.hbase.master.AssignmentManager;
032import org.apache.hadoop.hbase.util.Bytes;
033import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp;
034import org.apache.zookeeper.KeeperException;
035
036/**
037 * Helper class for table state tracking for use by {@link AssignmentManager}.
038 * Reads, caches and sets state up in zookeeper.  If multiple read/write
039 * clients, will make for confusion.  Read-only clients other than
040 * AssignmentManager interested in learning table state can use the
041 * read-only utility methods in {@link ZKTableReadOnly}.
042 *
043 * <p>To save on trips to the zookeeper ensemble, internally we cache table
044 * state.
045 */
046public class ZKTable {
047  // A znode will exist under the table directory if it is in any of the
048  // following states: {@link TableState#ENABLING} , {@link TableState#DISABLING},
049  // or {@link TableState#DISABLED}.  If {@link TableState#ENABLED}, there will
050  // be no entry for a table in zk.  Thats how it currently works.
051
052  private static final Log LOG = LogFactory.getLog(ZKTable.class);
053  private final ZooKeeperWatcher watcher;
054
055  /**
056   * Cache of what we found in zookeeper so we don't have to go to zk ensemble
057   * for every query.  Synchronize access rather than use concurrent Map because
058   * synchronization needs to span query of zk.
059   */
060  private final Map<String, TableState> cache =
061    new HashMap<String, TableState>();
062
063  // TODO: Make it so always a table znode. Put table schema here as well as table state.
064  // Have watcher on table znode so all are notified of state or schema change.
065  /**
066   * States a Table can be in.
067   * Compatibility note: ENABLED does not exist in 0.92 releases.  In 0.92, the absence of
068   * the znode indicates the table is enabled.
069   */
070  public static enum TableState {
071    ENABLED,
072    DISABLED,
073    DISABLING,
074    ENABLING
075  };
076
077  public ZKTable(final ZooKeeperWatcher zkw) throws KeeperException {
078    super();
079    this.watcher = zkw;
080    populateTableStates();
081  }
082
083  /**
084   * Gets a list of all the tables set as disabled in zookeeper.
085   * @throws KeeperException
086   */
087  private void populateTableStates()
088  throws KeeperException {
089    synchronized (this.cache) {
090      List<String> children =
091        ZKUtil.listChildrenNoWatch(this.watcher, this.watcher.masterTableZNode);
092      if (children == null) return;
093      for (String child: children) {
094        TableState state = getTableState(this.watcher, child);
095        if (state != null) this.cache.put(child, state);
096      }
097    }
098  }
099
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    return ZKTableReadOnly.getTableState(zkw, zkw.masterTableZNode, child);
110  }
111
112  /**
113   * Sets the specified table as DISABLED in zookeeper.  Fails silently if the
114   * table is already disabled in zookeeper.  Sets no watches.
115   * @param tableName
116   * @throws KeeperException unexpected zookeeper exception
117   */
118  public void setDisabledTable(String tableName)
119  throws KeeperException {
120    synchronized (this.cache) {
121      if (!isDisablingOrDisabledTable(tableName)) {
122        LOG.warn("Moving table " + tableName + " state to disabled but was " +
123          "not first in disabling state: " + this.cache.get(tableName));
124      }
125      setTableState(tableName, TableState.DISABLED);
126    }
127  }
128
129  /**
130   * Sets the specified table as DISABLING in zookeeper.  Fails silently if the
131   * table is already disabled in zookeeper.  Sets no watches.
132   * @param tableName
133   * @throws KeeperException unexpected zookeeper exception
134   */
135  public void setDisablingTable(final String tableName)
136  throws KeeperException {
137    synchronized (this.cache) {
138      if (!isEnabledOrDisablingTable(tableName)) {
139        LOG.warn("Moving table " + tableName + " state to disabling but was " +
140          "not first in enabled state: " + this.cache.get(tableName));
141      }
142      setTableState(tableName, TableState.DISABLING);
143    }
144  }
145
146  /**
147   * Sets the specified table as ENABLING in zookeeper.  Fails silently if the
148   * table is already disabled in zookeeper.  Sets no watches.
149   * @param tableName
150   * @throws KeeperException unexpected zookeeper exception
151   */
152  public void setEnablingTable(final String tableName)
153  throws KeeperException {
154    synchronized (this.cache) {
155      if (!isDisabledOrEnablingTable(tableName)) {
156        LOG.warn("Moving table " + tableName + " state to enabling but was " +
157          "not first in disabled state: " + this.cache.get(tableName));
158      }
159      setTableState(tableName, TableState.ENABLING);
160    }
161  }
162
163  /**
164   * Sets the specified table as ENABLING in zookeeper atomically
165   * If the table is already in ENABLING state, no operation is performed
166   * @param tableName
167   * @return if the operation succeeds or not
168   * @throws KeeperException unexpected zookeeper exception
169   */
170  public boolean checkAndSetEnablingTable(final String tableName)
171    throws KeeperException {
172    synchronized (this.cache) {
173      if (isEnablingOrEnabledTable(tableName)) {
174        return false;
175      }
176      setTableState(tableName, TableState.ENABLING);
177      return true;
178    }
179  }
180  
181  /**
182   * If the table is found in ENABLING state the inmemory state is removed.
183   * This helps in cases where CreateTable is to be retried by the client incase of failures.
184   * If deleteZNode is true - the znode is also deleted
185   * @param tableName
186   * @param deleteZNode
187   * @throws KeeperException
188   */
189  public void removeEnablingTable(final String tableName, boolean deleteZNode)
190      throws KeeperException {
191    synchronized (this.cache) {
192      if (isEnablingTable(tableName)) {
193        this.cache.remove(tableName);
194        if (deleteZNode) {
195          ZKUtil.deleteNodeFailSilent(this.watcher,
196              ZKUtil.joinZNode(this.watcher.masterTableZNode, tableName));
197        }
198      }
199
200    }
201  }
202
203  /**
204   * Sets the specified table as ENABLING in zookeeper atomically
205   * If the table isn't in DISABLED state, no operation is performed
206   * @param tableName
207   * @return if the operation succeeds or not
208   * @throws KeeperException unexpected zookeeper exception
209   */
210  public boolean checkDisabledAndSetEnablingTable(final String tableName)
211    throws KeeperException {
212    synchronized (this.cache) {
213      if (!isDisabledTable(tableName)) {
214        return false;
215      }
216      setTableState(tableName, TableState.ENABLING);
217      return true;
218    }
219  }
220
221  /**
222   * Sets the specified table as DISABLING in zookeeper atomically
223   * If the table isn't in ENABLED state, no operation is performed
224   * @param tableName
225   * @return if the operation succeeds or not
226   * @throws KeeperException unexpected zookeeper exception
227   */
228  public boolean checkEnabledAndSetDisablingTable(final String tableName)
229    throws KeeperException {
230    synchronized (this.cache) {
231      if (this.cache.get(tableName) != null && !isEnabledTable(tableName)) {
232        return false;
233      }
234      setTableState(tableName, TableState.DISABLING);
235      return true;
236    }
237  }
238
239  private void setTableState(final String tableName, final TableState state)
240  throws KeeperException {
241    String znode = ZKUtil.joinZNode(this.watcher.masterTableZNode, tableName);
242    if (ZKUtil.checkExists(this.watcher, znode) == -1) {
243      ZKUtil.createAndFailSilent(this.watcher, znode);
244    }
245    String znode92 = ZKUtil.joinZNode(this.watcher.masterTableZNode92, tableName);
246    boolean settingToEnabled = (state == TableState.ENABLED);
247    // 0.92 format znode differs in that it is deleted to represent ENABLED,
248    // so only create if we are not setting to enabled.
249    if (!settingToEnabled) {
250      if (ZKUtil.checkExists(this.watcher, znode92) == -1) {
251        ZKUtil.createAndFailSilent(this.watcher, znode92);
252      }
253    }
254    synchronized (this.cache) {
255      List<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
256      if (settingToEnabled) {
257        ops.add(ZKUtilOp.deleteNodeFailSilent(znode92));
258      }
259      else {
260        ops.add(ZKUtilOp.setData(znode92, Bytes.toBytes(state.toString())));
261      }
262      // If not running multi-update either because of configuration or failure,
263      // set the current format znode after the 0.92 format znode.
264      // This is so in the case of failure, the AssignmentManager is guaranteed to
265      // see the state was not applied, since it uses the current format znode internally.
266      ops.add(ZKUtilOp.setData(znode, Bytes.toBytes(state.toString())));
267      ZKUtil.multiOrSequential(this.watcher, ops, true);
268      this.cache.put(tableName, state);
269    }
270  }
271
272  public boolean isDisabledTable(final String tableName) {
273    return isTableState(tableName, TableState.DISABLED);
274  }
275
276  public boolean isDisablingTable(final String tableName) {
277    return isTableState(tableName, TableState.DISABLING);
278  }
279
280  public boolean isEnablingTable(final String tableName) {
281    return isTableState(tableName, TableState.ENABLING);
282  }
283
284  public boolean isEnabledTable(String tableName) {
285    return isTableState(tableName, TableState.ENABLED);
286  }
287
288  public boolean isDisablingOrDisabledTable(final String tableName) {
289    synchronized (this.cache) {
290      return isDisablingTable(tableName) || isDisabledTable(tableName);
291    }
292  }
293
294  public boolean isEnablingOrEnabledTable(final String tableName) {
295    synchronized (this.cache) {
296      return isEnablingTable(tableName) || isEnabledTable(tableName);
297    }
298  }
299
300  public boolean isEnabledOrDisablingTable(final String tableName) {
301    synchronized (this.cache) {
302      return isEnabledTable(tableName) || isDisablingTable(tableName);
303    }
304  }
305
306  public boolean isDisabledOrEnablingTable(final String tableName) {
307    synchronized (this.cache) {
308      return isDisabledTable(tableName) || isEnablingTable(tableName);
309    }
310  }
311
312  private boolean isTableState(final String tableName, final TableState state) {
313    synchronized (this.cache) {
314      TableState currentState = this.cache.get(tableName);
315      return ZKTableReadOnly.isTableState(currentState, state);
316    }
317  }
318
319  /**
320   * Deletes the table in zookeeper.  Fails silently if the
321   * table is not currently disabled in zookeeper.  Sets no watches.
322   * @param tableName
323   * @throws KeeperException unexpected zookeeper exception
324   */
325  public void setDeletedTable(final String tableName)
326  throws KeeperException {
327    synchronized (this.cache) {
328      List<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
329      ops.add(ZKUtilOp.deleteNodeFailSilent(
330        ZKUtil.joinZNode(this.watcher.masterTableZNode92, tableName)));
331      // If not running multi-update either because of configuration or failure,
332      // delete the current format znode after the 0.92 format znode.  This is so in the case of
333      // failure, the AssignmentManager is guaranteed to see the table was not deleted, since it
334      // uses the current format znode internally.
335      ops.add(ZKUtilOp.deleteNodeFailSilent(
336        ZKUtil.joinZNode(this.watcher.masterTableZNode, tableName)));
337      ZKUtil.multiOrSequential(this.watcher, ops, true);
338      if (this.cache.remove(tableName) == null) {
339        LOG.warn("Moving table " + tableName + " state to deleted but was " +
340          "already deleted");
341      }
342    }
343  }
344  
345  /**
346   * Sets the ENABLED state in the cache and creates or force updates a node to
347   * ENABLED state for the specified table
348   * 
349   * @param tableName
350   * @throws KeeperException
351   */
352  public void setEnabledTable(final String tableName) throws KeeperException {
353    setTableState(tableName, TableState.ENABLED);
354  }
355
356  /**
357   * check if table is present .
358   * 
359   * @param tableName
360   * @return true if the table is present
361   */
362  public boolean isTablePresent(final String tableName) {
363    synchronized (this.cache) {
364      TableState state = this.cache.get(tableName);
365      return !(state == null);
366    }
367  }
368  
369  /**
370   * Gets a list of all the tables set as disabled in zookeeper.
371   * @return Set of disabled tables, empty Set if none
372   */
373  public Set<String> getDisabledTables() {
374    Set<String> disabledTables = new HashSet<String>();
375    synchronized (this.cache) {
376      Set<String> tables = this.cache.keySet();
377      for (String table: tables) {
378        if (isDisabledTable(table)) disabledTables.add(table);
379      }
380    }
381    return disabledTables;
382  }
383
384}