From 626bca6ece1e4d043f2508cdffc870d6647ac114 Mon Sep 17 00:00:00 2001 From: Michael Stack Date: Tue, 5 Sep 2017 16:45:50 -0700 Subject: [PATCH 2/2] HBASE-16060 1.x clients cannot access table state talking to 2.0 cluster WIP. Adds back classes which can read/write ZK table state. TODO: Add mirroring of state (and tests). --- .../hadoop/hbase/zookeeper/ZooKeeperWatcher.java | 2 - .../main/java/org/apache/hadoop/hbase/Cell.java | 1 + .../hadoop/hbase/master/TableStateManager.java | 2 +- .../master/hbase1/HBase1TableStateManager.java | 134 ++++++++ .../master/hbase1/HBase1ZKTableStateManager.java | 382 +++++++++++++++++++++ .../hadoop/hbase/master/hbase1/package-info.java | 26 ++ 6 files changed, 544 insertions(+), 3 deletions(-) create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1TableStateManager.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1ZKTableStateManager.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/package-info.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java index 6bec3527c0..61049ce37e 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java @@ -34,8 +34,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Abortable; import org.apache.hadoop.hbase.AuthUtil; -import org.apache.hadoop.hbase.HConstants; -import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.ZooKeeperConnectionException; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.security.Superusers; diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/Cell.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/Cell.java index 8a701f2c1d..3d7f001beb 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/Cell.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/Cell.java @@ -57,6 +57,7 @@ import org.apache.hadoop.hbase.classification.InterfaceAudience; * consecutive bytes in the same byte[], whereas this interface allows fields to reside in separate * byte[]'s. *

+ * @see CellBuilder */ @InterfaceAudience.Public public interface Cell { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/TableStateManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/TableStateManager.java index fb83971cd0..a4e8e389e6 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/TableStateManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/TableStateManager.java @@ -42,7 +42,7 @@ import org.apache.hadoop.hbase.client.TableState; /** * This is a helper class used to manage table states. * States persisted in tableinfo and cached internally. - * TODO: Cache state. Cut down on meta looksups. + * TODO: Cache state. Cut down on meta lookups. */ @InterfaceAudience.Private public class TableStateManager { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1TableStateManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1TableStateManager.java new file mode 100644 index 0000000000..6f34d3e81f --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1TableStateManager.java @@ -0,0 +1,134 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.hbase1; + +import java.io.InterruptedIOException; +import java.util.Set; + +import org.apache.hadoop.hbase.CoordinatedStateException; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos; + +/** + * Helper class for table state management for operations running inside + * RegionServer or HMaster. Taken from hbase branch-1. We do table state management + * differently in hbase-2.x. Classes in this package are to mirror table state + * transitions so hbase-1.x clients can continue to run against a hbase-2.x cluster. + * + *

Depending on implementation, fetches information from HBase system table, + * local data store, ZooKeeper ensemble or somewhere else (only zk was implemented in hbase-1.x). + * + *

Code running on client side (with no coordinated state context) should use hbase1 + * org.apache.hadoop.hbase.zookeeper.ZKTableStateClientSideReader, not these classes, when + * reading. + * + * @deprecated In hbase-2.0.0. Remove in hbase-3.0.0 + */ +@Deprecated +@InterfaceAudience.Private +public interface HBase1TableStateManager { + + /** + * Sets the table into desired state. Fails silently if the table is already in this state. + * @param tableName table to process + * @param state new state of this table + * @throws CoordinatedStateException if error happened when trying to set table state + */ + void setTableState(TableName tableName, ZooKeeperProtos.DeprecatedTableState.State state) + throws CoordinatedStateException; + + /** + * Sets the specified table into the newState, but only if the table is already in + * one of the possibleCurrentStates (otherwise no operation is performed). + * @param tableName table to process + * @param newState new state for the table + * @param states table should be in one of these states for the operation + * to be performed + * @throws CoordinatedStateException if error happened while performing operation + * @return true if operation succeeded, false otherwise + */ + boolean setTableStateIfInStates(TableName tableName, + ZooKeeperProtos.DeprecatedTableState.State newState, + ZooKeeperProtos.DeprecatedTableState.State... states) + throws CoordinatedStateException; + + /** + * Sets the specified table into the newState, but only if the table is NOT in + * one of the possibleCurrentStates (otherwise no operation is performed). + * @param tableName table to process + * @param newState new state for the table + * @param states table should NOT be in one of these states for the operation + * to be performed + * @throws CoordinatedStateException if error happened while performing operation + * @return true if operation succeeded, false otherwise + */ + boolean setTableStateIfNotInStates(TableName tableName, + ZooKeeperProtos.DeprecatedTableState.State newState, + ZooKeeperProtos.DeprecatedTableState.State... states) + throws CoordinatedStateException; + + /** + * @return true if the table is in any one of the listed states, false otherwise. + */ + boolean isTableState(TableName tableName, ZooKeeperProtos.DeprecatedTableState.State... states); + + /** + * @return true if the table is in any one of the listed states, false otherwise. + */ + boolean isTableState(TableName tableName, boolean checkSource, + ZooKeeperProtos.DeprecatedTableState.State... states); + + /** + * Mark table as deleted. Fails silently if the table is not currently marked as disabled. + * @param tableName table to be deleted + * @throws CoordinatedStateException if error happened while performing operation + */ + void setDeletedTable(TableName tableName) throws CoordinatedStateException; + + /** + * Checks if table is present. + * + * @param tableName table we're checking + * @return true if the table is present, false otherwise + */ + boolean isTablePresent(TableName tableName); + + /** + * @return set of tables which are in any one of the listed states, empty Set if none + */ + Set getTablesInStates(ZooKeeperProtos.DeprecatedTableState.State... states) + throws InterruptedIOException, CoordinatedStateException; + + /** + * If the table is found in the given state the in-memory state is removed. This + * helps in cases where CreateTable is to be retried by the client in case of + * failures. If deletePermanentState is true - the flag kept permanently is + * also reset. + * + * @param tableName table we're working on + * @param states if table isn't in any one of these states, operation aborts + * @param deletePermanentState if true, reset the permanent flag + * @throws CoordinatedStateException if error happened in underlying coordination engine + */ + void checkAndRemoveTableState(TableName tableName, + ZooKeeperProtos.DeprecatedTableState.State states, + boolean deletePermanentState) + throws CoordinatedStateException; +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1ZKTableStateManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1ZKTableStateManager.java new file mode 100644 index 0000000000..28edb17379 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/HBase1ZKTableStateManager.java @@ -0,0 +1,382 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.master.hbase1; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.CoordinatedStateException; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos; +import org.apache.hadoop.hbase.zookeeper.ZKUtil; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; +import org.apache.zookeeper.KeeperException; + +/** + * Implementation of TableStateManager which reads, caches and sets state + * up in ZooKeeper. If multiple read/write clients, will make for confusion. + * + *

To save on trips to the zookeeper ensemble, internally we cache table + * state. + * + *

Code running on client side (with no coordinated state context) should use hbase1 + * org.apache.hadoop.hbase.zookeeper.ZKTableStateClientSideReader, not these classes, when + * reading. + * + * @deprecated In hbase-2.0.0. Remove in hbase-3.0.0 + */ +@Deprecated +@InterfaceAudience.Private +public class HBase1ZKTableStateManager implements HBase1TableStateManager { + // A znode will exist under the table directory if it is in any of the + // following states: {@link TableState#ENABLING} , {@link TableState#DISABLING}, + // or {@link TableState#DISABLED}. If {@link TableState#ENABLED}, there will + // be no entry for a table in zk. Thats how it currently works. + + private static final Log LOG = LogFactory.getLog(HBase1ZKTableStateManager.class); + private final ZooKeeperWatcher watcher; + + /** + * Cache of what we found in zookeeper so we don't have to go to zk ensemble + * for every query. Synchronize access rather than use concurrent Map because + * synchronization needs to span query of zk. + */ + private final Map cache = + new HashMap(); + + public HBase1ZKTableStateManager(final ZooKeeperWatcher zkw) throws KeeperException, + InterruptedException { + super(); + this.watcher = zkw; + populateTableStates(); + } + + /** + * Gets a list of all the tables set as disabled in zookeeper. + * @throws KeeperException, InterruptedException + */ + private void populateTableStates() throws KeeperException, InterruptedException { + synchronized (this.cache) { + List children = ZKUtil.listChildrenNoWatch(this.watcher, + this.watcher.znodePaths.tableZNode); + if (children == null) return; + for (String child: children) { + TableName tableName = TableName.valueOf(child); + ZooKeeperProtos.DeprecatedTableState.State state = getTableState(this.watcher, tableName); + if (state != null) this.cache.put(tableName, state); + } + } + } + + /** + * Sets table state in ZK. Sets no watches. + * + * {@inheritDoc} + */ + @Override + public void setTableState(TableName tableName, ZooKeeperProtos.DeprecatedTableState.State state) + throws CoordinatedStateException { + synchronized (this.cache) { + LOG.info("Moving table " + tableName + " state from " + this.cache.get(tableName) + + " to " + state); + try { + setTableStateInZK(tableName, state); + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } + } + } + + /** + * Checks and sets table state in ZK. Sets no watches. + * {@inheritDoc} + */ + @Override + public boolean setTableStateIfInStates(TableName tableName, + ZooKeeperProtos.DeprecatedTableState.State newState, + ZooKeeperProtos.DeprecatedTableState.State... states) + throws CoordinatedStateException { + synchronized (this.cache) { + // Transition ENABLED->DISABLING has to be performed with a hack, because + // we treat empty state as enabled in this case because 0.92- clusters. + if ( + (newState == ZooKeeperProtos.DeprecatedTableState.State.DISABLING) && + this.cache.get(tableName) != null && !isTableState(tableName, states) || + (newState != ZooKeeperProtos.DeprecatedTableState.State.DISABLING && + !isTableState(tableName, states) )) { + return false; + } + try { + setTableStateInZK(tableName, newState); + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } + return true; + } + } + + /** + * Checks and sets table state in ZK. Sets no watches. + * {@inheritDoc} + */ + @Override + public boolean setTableStateIfNotInStates(TableName tableName, + ZooKeeperProtos.DeprecatedTableState.State newState, + ZooKeeperProtos.DeprecatedTableState.State... states) + throws CoordinatedStateException { + synchronized (this.cache) { + if (isTableState(tableName, states)) { + // If the table is in the one of the states from the states list, the cache + // might be out-of-date, try to find it out from the master source (zookeeper server). + // + // Note: this adds extra zookeeper server calls and might have performance impact. + // However, this is not the happy path so we should not reach here often. Therefore, + // the performance impact should be minimal to none. + try { + ZooKeeperProtos.DeprecatedTableState.State curstate = getTableState(watcher, tableName); + + if (isTableInState(Arrays.asList(states), curstate)) { + return false; + } + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } catch (InterruptedException e) { + throw new CoordinatedStateException(e); + } + } + try { + setTableStateInZK(tableName, newState); + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } + return true; + } + } + + private void setTableStateInZK(final TableName tableName, + final ZooKeeperProtos.DeprecatedTableState.State state) + throws KeeperException { + String znode = ZKUtil.joinZNode(this.watcher.znodePaths.tableZNode, + tableName.getNameAsString()); + if (ZKUtil.checkExists(this.watcher, znode) == -1) { + ZKUtil.createAndFailSilent(this.watcher, znode); + } + synchronized (this.cache) { + ZooKeeperProtos.DeprecatedTableState.Builder builder = + ZooKeeperProtos.DeprecatedTableState.newBuilder(); + builder.setState(state); + byte [] data = ProtobufUtil.prependPBMagic(builder.build().toByteArray()); + ZKUtil.setData(this.watcher, znode, data); + this.cache.put(tableName, state); + } + } + + /** + * Checks if table is marked in specified state in ZK (using cache only). {@inheritDoc} + */ + @Override + public boolean isTableState(final TableName tableName, + final ZooKeeperProtos.DeprecatedTableState.State... states) { + return isTableState(tableName, false, states); // only check cache + } + + /** + * Checks if table is marked in specified state in ZK. {@inheritDoc} + */ + @Override + public boolean isTableState(final TableName tableName, final boolean checkSource, + final ZooKeeperProtos.DeprecatedTableState.State... states) { + boolean isTableInSpecifiedState; + synchronized (this.cache) { + ZooKeeperProtos.DeprecatedTableState.State currentState = this.cache.get(tableName); + if (checkSource) { + // The cache might be out-of-date, try to find it out from the master source (zookeeper + // server) and update the cache. + try { + ZooKeeperProtos.DeprecatedTableState.State stateInZK = getTableState(watcher, tableName); + + if (currentState != stateInZK) { + if (stateInZK != null) { + this.cache.put(tableName, stateInZK); + } else { + this.cache.remove(tableName); + } + currentState = stateInZK; + } + } catch (KeeperException | InterruptedException e) { + // Contacting zookeeper failed. Let us just trust the value in cache. + } + } + return isTableInState(Arrays.asList(states), currentState); + } + } + + /** + * Deletes the table in zookeeper. Fails silently if the table is not currently disabled in + * zookeeper. Sets no watches. {@inheritDoc} + */ + @Override + public void setDeletedTable(final TableName tableName) + throws CoordinatedStateException { + synchronized (this.cache) { + if (this.cache.remove(tableName) == null) { + LOG.warn("Moving table " + tableName + " state to deleted but was already deleted"); + } + try { + ZKUtil.deleteNodeFailSilent(this.watcher, + ZKUtil.joinZNode(this.watcher.znodePaths.tableZNode, tableName.getNameAsString())); + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } + } + } + + /** + * check if table is present. + * + * @param tableName table we're working on + * @return true if the table is present + */ + @Override + public boolean isTablePresent(final TableName tableName) { + synchronized (this.cache) { + ZooKeeperProtos.DeprecatedTableState.State state = this.cache.get(tableName); + return !(state == null); + } + } + + /** + * Gets a list of all the tables set as disabling in zookeeper. + * @return Set of disabling tables, empty Set if none + * @throws CoordinatedStateException if error happened in underlying coordination engine + */ + @Override + public Set getTablesInStates(ZooKeeperProtos.DeprecatedTableState.State... states) + throws InterruptedIOException, CoordinatedStateException { + try { + return getAllTables(states); + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void checkAndRemoveTableState(TableName tableName, + ZooKeeperProtos.DeprecatedTableState.State states, + boolean deletePermanentState) + throws CoordinatedStateException { + synchronized (this.cache) { + if (isTableState(tableName, states)) { + this.cache.remove(tableName); + if (deletePermanentState) { + try { + ZKUtil.deleteNodeFailSilent(this.watcher, + ZKUtil.joinZNode(this.watcher.znodePaths.tableZNode, + tableName.getNameAsString())); + } catch (KeeperException e) { + throw new CoordinatedStateException(e); + } + } + } + } + } + + /** + * Gets a list of all the tables of specified states in zookeeper. + * @return Set of tables of specified states, empty Set if none + * @throws KeeperException + */ + Set getAllTables(final ZooKeeperProtos.DeprecatedTableState.State... states) + throws KeeperException, InterruptedIOException { + + Set allTables = new HashSet(); + List children = + ZKUtil.listChildrenNoWatch(watcher, this.watcher.znodePaths.tableZNode); + if(children == null) return allTables; + for (String child: children) { + TableName tableName = TableName.valueOf(child); + ZooKeeperProtos.DeprecatedTableState.State state; + try { + state = getTableState(watcher, tableName); + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } + for (ZooKeeperProtos.DeprecatedTableState.State expectedState: states) { + if (state == expectedState) { + allTables.add(tableName); + break; + } + } + } + return allTables; + } + + /** + * Gets table state from ZK. + * @param zkw ZooKeeperWatcher instance to use + * @param tableName table we're checking + * @return Null or {@link ZooKeeperProtos.DeprecatedTableState.State} found in znode. + * @throws KeeperException + */ + private ZooKeeperProtos.DeprecatedTableState.State getTableState(final ZooKeeperWatcher zkw, + final TableName tableName) + throws KeeperException, InterruptedException { + String znode = ZKUtil.joinZNode(this.watcher.znodePaths.tableZNode, + tableName.getNameAsString()); + byte [] data = ZKUtil.getData(zkw, znode); + if (data == null || data.length <= 0) return null; + try { + ProtobufUtil.expectPBMagicPrefix(data); + ZooKeeperProtos.DeprecatedTableState.Builder builder = + ZooKeeperProtos.DeprecatedTableState.newBuilder(); + int magicLen = ProtobufUtil.lengthOfPBMagic(); + ProtobufUtil.mergeFrom(builder, data, magicLen, data.length - magicLen); + return builder.getState(); + } catch (IOException e) { + KeeperException ke = new KeeperException.DataInconsistencyException(); + ke.initCause(e); + throw ke; + } catch (DeserializationException e) { + throw ZKUtil.convert(e); + } + } + + /** + * @return true if current state isn't null and is contained + * in the list of expected states. + */ + private boolean isTableInState(final List expectedStates, + final ZooKeeperProtos.DeprecatedTableState.State currentState) { + return currentState != null && expectedStates.contains(currentState); + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/package-info.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/package-info.java new file mode 100644 index 0000000000..8b4d360334 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/hbase1/package-info.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Provides classes that allow us echo state needed by branch-1 (hbase-1.x) + * clients. For most part, classes in this package come from branch-1. + * An example of something we do to make it so branch-1 clients can continue + * to do basic operations against an hbase-2.x cluster is our echoing of + * table state to zookeeper; table state is kept in a table in hbase-2.x but + * hbase-1.x clients look to zookeeper to read table state (ENABLED or DISABLED). + */ +package org.apache.hadoop.hbase.master.hbase1; -- 2.11.0 (Apple Git-81)