001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master;
019
020import edu.umd.cs.findbugs.annotations.NonNull;
021import edu.umd.cs.findbugs.annotations.Nullable;
022import java.io.IOException;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028import java.util.concurrent.locks.ReadWriteLock;
029import org.apache.hadoop.hbase.MetaTableAccessor;
030import org.apache.hadoop.hbase.TableDescriptors;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.TableNotFoundException;
033import org.apache.hadoop.hbase.client.Connection;
034import org.apache.hadoop.hbase.client.Result;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableState;
037import org.apache.hadoop.hbase.exceptions.IllegalArgumentIOException;
038import org.apache.hadoop.hbase.util.IdReadWriteLock;
039import org.apache.hadoop.hbase.util.IdReadWriteLockWithObjectPool;
040import org.apache.hadoop.hbase.util.ZKDataMigrator;
041import org.apache.hadoop.hbase.zookeeper.ZKUtil;
042import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
043import org.apache.yetus.audience.InterfaceAudience;
044import org.apache.zookeeper.KeeperException;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
049
050/**
051 * This is a helper class used to manage table states. This class uses hbase:meta as its store for
052 * table state so hbase:meta must be online before {@link #start()} is called.
053 */
054// TODO: Make this a guava Service
055@InterfaceAudience.Private
056public class TableStateManager {
057
058  private static final Logger LOG = LoggerFactory.getLogger(TableStateManager.class);
059  /**
060   * Set this key to false in Configuration to disable migrating table state from zookeeper so
061   * hbase:meta table.
062   */
063  private static final String MIGRATE_TABLE_STATE_FROM_ZK_KEY =
064    "hbase.migrate.table.state.from.zookeeper";
065
066  private final IdReadWriteLock<TableName> tnLock = new IdReadWriteLockWithObjectPool<>();
067  protected final MasterServices master;
068
069  private final ConcurrentMap<TableName, TableState.State> tableName2State =
070    new ConcurrentHashMap<>();
071
072  TableStateManager(MasterServices master) {
073    this.master = master;
074  }
075
076  /**
077   * Set table state to provided. Caller should lock table on write.
078   * @param tableName table to change state for
079   * @param newState new state
080   */
081  public void setTableState(TableName tableName, TableState.State newState) throws IOException {
082    ReadWriteLock lock = tnLock.getLock(tableName);
083    lock.writeLock().lock();
084    try {
085      updateMetaState(tableName, newState);
086    } finally {
087      lock.writeLock().unlock();
088    }
089  }
090
091  public boolean isTableState(TableName tableName, TableState.State... states) {
092    try {
093      TableState tableState = getTableState(tableName);
094      return tableState.isInStates(states);
095    } catch (IOException e) {
096      LOG.error("Unable to get table " + tableName + " state", e);
097      // XXX: is it safe to just return false here?
098      return false;
099    }
100  }
101
102  public void setDeletedTable(TableName tableName) throws IOException {
103    if (tableName.equals(TableName.META_TABLE_NAME)) {
104      // Can't delete the hbase:meta table.
105      return;
106    }
107    ReadWriteLock lock = tnLock.getLock(tableName);
108    lock.writeLock().lock();
109    try {
110      MetaTableAccessor.deleteTableState(master.getConnection(), tableName);
111      metaStateDeleted(tableName);
112    } finally {
113      tableName2State.remove(tableName);
114      lock.writeLock().unlock();
115    }
116  }
117
118  public boolean isTablePresent(TableName tableName) throws IOException {
119    ReadWriteLock lock = tnLock.getLock(tableName);
120    lock.readLock().lock();
121    try {
122      return readMetaState(tableName) != null;
123    } finally {
124      lock.readLock().unlock();
125    }
126  }
127
128  /**
129   * Return all tables in given states.
130   * @param states filter by states
131   * @return tables in given states
132   */
133  Set<TableName> getTablesInStates(TableState.State... states) throws IOException {
134    // Only be called in region normalizer, will not use cache.
135    final Set<TableName> rv = Sets.newHashSet();
136    MetaTableAccessor.fullScanTables(master.getConnection(), new MetaTableAccessor.Visitor() {
137      @Override
138      public boolean visit(Result r) throws IOException {
139        TableState tableState = MetaTableAccessor.getTableState(r);
140        if (tableState != null && tableState.inStates(states)) {
141          rv.add(tableState.getTableName());
142        }
143        return true;
144      }
145    });
146    return rv;
147  }
148
149  @NonNull
150  public TableState getTableState(TableName tableName) throws IOException {
151    ReadWriteLock lock = tnLock.getLock(tableName);
152    lock.readLock().lock();
153    try {
154      TableState currentState = readMetaState(tableName);
155      if (currentState == null) {
156        throw new TableNotFoundException("No state found for " + tableName);
157      }
158      return currentState;
159    } finally {
160      lock.readLock().unlock();
161    }
162  }
163
164  private void updateMetaState(TableName tableName, TableState.State newState) throws IOException {
165    if (tableName.equals(TableName.META_TABLE_NAME)) {
166      if (TableState.State.DISABLING.equals(newState) ||
167          TableState.State.DISABLED.equals(newState)) {
168        throw new IllegalArgumentIOException("Cannot disable meta table; " + newState);
169      }
170      // Otherwise, just return; no need to set ENABLED on meta -- it is always ENABLED.
171      return;
172    }
173    boolean succ = false;
174    try {
175      MetaTableAccessor.updateTableState(master.getConnection(), tableName, newState);
176      tableName2State.put(tableName, newState);
177      succ = true;
178    } finally {
179      if (!succ) {
180        this.tableName2State.remove(tableName);
181      }
182    }
183    metaStateUpdated(tableName, newState);
184  }
185
186  protected void metaStateUpdated(TableName tableName, TableState.State newState)
187      throws IOException {
188  }
189
190  protected void metaStateDeleted(TableName tableName) throws IOException {
191  }
192
193  @Nullable
194  private TableState readMetaState(TableName tableName) throws IOException {
195    TableState.State state = tableName2State.get(tableName);
196    if (state != null) {
197      return new TableState(tableName, state);
198    }
199    TableState tableState = MetaTableAccessor.getTableState(master.getConnection(), tableName);
200    if (tableState != null) {
201      tableName2State.putIfAbsent(tableName, tableState.getState());
202    }
203    return tableState;
204  }
205
206  public void start() throws IOException {
207    migrateZooKeeper();
208    fixTableStates(master.getTableDescriptors(), master.getConnection());
209  }
210
211  private void fixTableStates(TableDescriptors tableDescriptors, Connection connection)
212      throws IOException {
213    Map<String, TableState> states = new HashMap<>();
214    // NOTE: Full hbase:meta table scan!
215    MetaTableAccessor.fullScanTables(connection, new MetaTableAccessor.Visitor() {
216      @Override
217      public boolean visit(Result r) throws IOException {
218        TableState state = MetaTableAccessor.getTableState(r);
219        states.put(state.getTableName().getNameAsString(), state);
220        return true;
221      }
222    });
223    for (TableDescriptor tableDesc : tableDescriptors.getAll().values()) {
224      TableName tableName = tableDesc.getTableName();
225      if (TableName.isMetaTableName(tableName)) {
226        // This table is always enabled. No fixup needed. No entry in hbase:meta needed.
227        // Call through to fixTableState though in case a super class wants to do something.
228        fixTableState(new TableState(tableName, TableState.State.ENABLED));
229        continue;
230      }
231      TableState tableState = states.get(tableName.getNameAsString());
232      if (tableState == null) {
233        LOG.warn(tableName + " has no table state in hbase:meta, assuming ENABLED");
234        MetaTableAccessor.updateTableState(connection, tableName, TableState.State.ENABLED);
235        fixTableState(new TableState(tableName, TableState.State.ENABLED));
236        tableName2State.put(tableName, TableState.State.ENABLED);
237      } else {
238        fixTableState(tableState);
239        tableName2State.put(tableName, tableState.getState());
240      }
241    }
242  }
243
244  /**
245   * For subclasses in case they want to do fixup post hbase:meta.
246   */
247  protected void fixTableState(TableState tableState) throws IOException {
248  }
249
250  /**
251   * This code is for case where a hbase2 Master is starting for the first time. ZooKeeper is where
252   * we used to keep table state. On first startup, read zookeeper and update hbase:meta with the
253   * table states found in zookeeper. This is tricky as we'll do this check every time we startup
254   * until mirroring is disabled. See the {@link #MIGRATE_TABLE_STATE_FROM_ZK_KEY} flag. Original
255   * form of this migration came in with HBASE-13032. It deleted all znodes when done. We can't do
256   * that if we want to support hbase-1.x clients who need to be able to read table state out of zk.
257   * See {@link MirroringTableStateManager}.
258   * @deprecated Since 2.0.0. Remove in hbase-3.0.0.
259   */
260  @Deprecated
261  private void migrateZooKeeper() throws IOException {
262    if (!this.master.getConfiguration().getBoolean(MIGRATE_TABLE_STATE_FROM_ZK_KEY, true)) {
263      return;
264    }
265    try {
266      for (Map.Entry<TableName, TableState.State> entry : ZKDataMigrator
267        .queryForTableStates(this.master.getZooKeeper()).entrySet()) {
268        if (this.master.getTableDescriptors().get(entry.getKey()) == null) {
269          deleteZooKeeper(entry.getKey());
270          LOG.info("Purged table state entry from zookeepr for table not in hbase:meta: " +
271            entry.getKey());
272          continue;
273        }
274        TableState ts = null;
275        try {
276          ts = getTableState(entry.getKey());
277        } catch (TableNotFoundException e) {
278          // This can happen; table exists but no TableState.
279        }
280        if (ts == null) {
281          TableState.State zkstate = entry.getValue();
282          // Only migrate if it is an enable or disabled table. If in-between -- ENABLING or
283          // DISABLING then we have a problem; we are starting up an hbase-2 on a cluster with
284          // RIT. It is going to be rough!
285          if (zkstate.equals(TableState.State.ENABLED) ||
286            zkstate.equals(TableState.State.DISABLED)) {
287            LOG.info("Migrating table state from zookeeper to hbase:meta; tableName=" +
288              entry.getKey() + ", state=" + entry.getValue());
289            updateMetaState(entry.getKey(), entry.getValue());
290          } else {
291            LOG.warn("Table={} has no state and zookeeper state is in-between={} (neither " +
292              "ENABLED or DISABLED); NOT MIGRATING table state", entry.getKey(), zkstate);
293          }
294        }
295        // What if the table states disagree? Defer to the hbase:meta setting rather than have the
296        // hbase-1.x support prevail.
297      }
298    } catch (KeeperException | InterruptedException e) {
299      LOG.warn("Failed reading table state from zookeeper", e);
300    }
301  }
302
303  /**
304   * Utility method that knows how to delete the old hbase-1.x table state znode. Used also by the
305   * Mirroring subclass.
306   * @deprecated Since 2.0.0. To be removed in hbase-3.0.0.
307   */
308  @Deprecated
309  protected void deleteZooKeeper(TableName tableName) {
310    try {
311      // Delete from ZooKeeper
312      String znode = ZNodePaths.joinZNode(this.master.getZooKeeper().getZNodePaths().tableZNode,
313        tableName.getNameAsString());
314      ZKUtil.deleteNodeFailSilent(this.master.getZooKeeper(), znode);
315    } catch (KeeperException e) {
316      LOG.warn("Failed deleting table state from zookeeper", e);
317    }
318  }
319}