/**
* Copyright 2011 The Apache Software Foundation
*
* 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.index.coprocessor.master;
import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableDescriptors;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.catalog.MetaReader;
import org.apache.hadoop.hbase.coprocessor.BaseMasterObserver;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.MasterObserverExt;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.index.Column;
import org.apache.hadoop.hbase.index.ColumnQualifier;
import org.apache.hadoop.hbase.index.ColumnQualifier.ValueType;
import org.apache.hadoop.hbase.index.Constants;
import org.apache.hadoop.hbase.index.IndexSpecification;
import org.apache.hadoop.hbase.index.IndexedHTableDescriptor;
import org.apache.hadoop.hbase.index.SecIndexLoadBalancer;
import org.apache.hadoop.hbase.index.manager.IndexManager;
import org.apache.hadoop.hbase.index.util.IndexUtils;
import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
import org.apache.hadoop.hbase.master.AssignmentManager;
import org.apache.hadoop.hbase.master.AssignmentManager.RegionState;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.RegionPlan;
import org.apache.hadoop.hbase.master.handler.CreateTableHandler;
import org.apache.hadoop.hbase.master.handler.DeleteTableHandler;
import org.apache.hadoop.hbase.master.handler.DisableTableHandler;
import org.apache.hadoop.hbase.master.handler.EnableTableHandler;
import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.FSTableDescriptors;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.zookeeper.ZKTable;
/**
* Defines of coprocessor hooks(to support secondary indexing) of operations on
* {@link org.apache.hadoop.hbase.master.HMaster} process.
*/
public class IndexMasterObserver extends BaseMasterObserver implements MasterObserverExt {
private static final Log LOG = LogFactory.getLog(IndexMasterObserver.class.getName());
/*
* (non-Javadoc)
* @see org.apache.hadoop.hbase.coprocessor.BaseMasterObserver#preCreateTable(org
* .apache.hadoop.hbase.coprocessor.ObserverContext, org.apache.hadoop.hbase.HTableDescriptor,
* org.apache.hadoop.hbase.HRegionInfo[])
*/
@Override
public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
LOG.info("Entered into preCreateTable.");
MasterServices master = ctx.getEnvironment().getMasterServices();
if (desc instanceof IndexedHTableDescriptor) {
Map<Column, Pair<ValueType, Integer>> indexColDetails =
new HashMap<Column, Pair<ValueType, Integer>>();
String tableName = desc.getNameAsString();
checkEndsWithIndexSuffix(tableName);
String indexTableName = IndexUtils.getIndexTableName(tableName);
List<IndexSpecification> indices = ((IndexedHTableDescriptor) desc).getIndices();
// Even if indices list is empty,it will create index table also.
if (indices.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Empty indices. Index table may not created"
+ " if master goes down in between user table creation");
}
}
LOG.trace("Checking whether column families in "
+ "index specification are in actual table column familes.");
for (IndexSpecification iSpec : indices) {
checkColumnsForValidityAndConsistency(desc, iSpec, indexColDetails);
}
LOG.trace("Column families in index specifications " + "are in actual table column familes.");
boolean isTableExists = MetaReader.tableExists(master.getCatalogTracker(), tableName);
boolean isIndexTableExists =
MetaReader.tableExists(master.getCatalogTracker(), indexTableName);
if (isTableExists && isIndexTableExists) {
throw new TableExistsException("Table " + tableName + " already exist.");
} else if (isIndexTableExists) {
disableAndDeleteTable(master, indexTableName);
}
}
LOG.info("Exiting from preCreateTable.");
}
@Override
public void postModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
byte[] tableName, HTableDescriptor htd) throws IOException {
String table = Bytes.toString(tableName);
MasterServices master = ctx.getEnvironment().getMasterServices();
List<Pair<HRegionInfo, ServerName>> tableRegionsAndLocations = null;
LOG.info("Entering postModifyTable for the table " + table);
if (htd instanceof IndexedHTableDescriptor) {
TableDescriptors tableDescriptors = master.getTableDescriptors();
Map<String, HTableDescriptor> allTableDesc = tableDescriptors.getAll();
String indexTableName = IndexUtils.getIndexTableName(tableName);
if (allTableDesc.containsKey(indexTableName)) {
// Do table modification
List<IndexSpecification> indices = ((IndexedHTableDescriptor) htd).getIndices();
if (indices.isEmpty()) {
LOG.error("Empty indices are passed to modify the table " + Bytes.toString(tableName));
return;
}
IndexManager idxManager = IndexManager.getInstance();
idxManager.removeIndices(table);
idxManager.addIndexForTable(table, indices);
LOG.info("Successfully updated the indexes for the table " + table + " to " + indices);
} else {
try {
tableRegionsAndLocations =
MetaReader.getTableRegionsAndLocations(master.getCatalogTracker(), tableName, true);
} catch (InterruptedException e) {
LOG.error("Exception while trying to create index table for the existing table " + table);
return;
}
if (tableRegionsAndLocations != null) {
HRegionInfo[] regionInfo = new HRegionInfo[tableRegionsAndLocations.size()];
for (int i = 0; i < tableRegionsAndLocations.size(); i++) {
regionInfo[i] = tableRegionsAndLocations.get(i).getFirst();
}
byte[][] splitKeys = IndexUtils.getSplitKeys(regionInfo);
IndexedHTableDescriptor iDesc = (IndexedHTableDescriptor) htd;
createSecondaryIndexTable(iDesc, splitKeys, master, true);
}
}
}
LOG.info("Exiting postModifyTable for the table " + table);
}
private void checkColumnsForValidityAndConsistency(HTableDescriptor desc,
IndexSpecification iSpec, Map<Column, Pair<ValueType, Integer>> indexColDetails)
throws IOException {
for (ColumnQualifier cq : iSpec.getIndexColumns()) {
if (null == desc.getFamily(cq.getColumnFamily())) {
String message =
"Column family " + cq.getColumnFamilyString() + " in index specification "
+ iSpec.getName() + " not in Column families of table " + desc.getNameAsString()
+ '.';
LOG.error(message);
IllegalArgumentException ie = new IllegalArgumentException(message);
throw new IOException(ie);
}
Column column = new Column(cq.getColumnFamily(), cq.getQualifier(), cq.getValuePartition());
ValueType type = cq.getType();
int maxlength = cq.getMaxValueLength();
Pair<ValueType, Integer> colDetail = indexColDetails.get(column);
if (null != colDetail) {
if (!colDetail.getFirst().equals(type) || colDetail.getSecond() != maxlength) {
throw new IOException("ValueType/max value length of column " + column
+ " not consistent across the indices");
}
} else {
indexColDetails.put(column, new Pair<ValueType, Integer>(type, maxlength));
}
}
}
private void checkEndsWithIndexSuffix(String tableName) throws IOException {
if (tableName.endsWith(Constants.INDEX_TABLE_SUFFIX)) {
String message =
"User table name should not be ends with " + Constants.INDEX_TABLE_SUFFIX + '.';
LOG.error(message);
IllegalArgumentException ie = new IllegalArgumentException(message);
throw new IOException(ie);
}
}
private void disableAndDeleteTable(MasterServices master, String tableName) throws IOException {
byte[] tableNameInBytes = Bytes.toBytes(tableName);
LOG.error(tableName + " already exists. Disabling and deleting table " + tableName + '.');
boolean disabled = master.getAssignmentManager().getZKTable().isDisabledTable(tableName);
if (false == disabled) {
LOG.info("Disabling table " + tableName + '.');
new DisableTableHandler(master, tableNameInBytes, master.getCatalogTracker(),
master.getAssignmentManager(), false).process();
if (false == master.getAssignmentManager().getZKTable().isDisabledTable(tableName)) {
throw new IOException("Table " + tableName + " not disabled.");
}
}
LOG.info("Disabled table " + tableName + '.');
LOG.info("Deleting table " + tableName + '.');
new DeleteTableHandler(tableNameInBytes, master, master).process();
if (true == MetaReader.tableExists(master.getCatalogTracker(), tableName)) {
throw new IOException("Table " + tableName + " not deleted.");
}
LOG.info("Deleted table " + tableName + '.');
}
@Override
public void postCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
LOG.info("Entered into postCreateTableHandler of table " + desc.getNameAsString() + '.');
if (desc instanceof IndexedHTableDescriptor) {
MasterServices master = ctx.getEnvironment().getMasterServices();
byte[][] splitKeys = IndexUtils.getSplitKeys(regions);
// In case of post call for the index table creation, it wont be
// IndexedHTableDescriptor
IndexedHTableDescriptor iDesc = (IndexedHTableDescriptor) desc;
createSecondaryIndexTable(iDesc, splitKeys, master, false);
// if there is any user scenarios
// we can add index datails to index manager
}
LOG.info("Exiting from postCreateTableHandler of table " + desc.getNameAsString() + '.');
}
/**
* @param IndexedHTableDescriptor iDesc
* @param HRegionInfo [] regions
* @param MasterServices master
* @throws NotAllMetaRegionsOnlineException
* @throws IOException
*/
private void createSecondaryIndexTable(IndexedHTableDescriptor iDesc, byte[][] splitKeys,
MasterServices master, boolean disableTable) throws NotAllMetaRegionsOnlineException,
IOException {
String indexTableName = IndexUtils.getIndexTableName(iDesc.getNameAsString());
LOG.info("Creating secondary index table " + indexTableName + " for table "
+ iDesc.getNameAsString() + '.');
HTableDescriptor indexTableDesc = new HTableDescriptor(indexTableName);
HColumnDescriptor columnDescriptor = new HColumnDescriptor(Constants.IDX_COL_FAMILY);
String dataBlockEncodingAlgo =
master.getConfiguration().get("index.data.block.encoding.algo", "NONE");
DataBlockEncoding[] values = DataBlockEncoding.values();
for (DataBlockEncoding dataBlockEncoding : values) {
if (dataBlockEncoding.toString().equals(dataBlockEncodingAlgo)) {
columnDescriptor.setDataBlockEncoding(dataBlockEncoding);
}
}
indexTableDesc.addFamily(columnDescriptor);
indexTableDesc.setValue(HTableDescriptor.SPLIT_POLICY,
ConstantSizeRegionSplitPolicy.class.getName());
indexTableDesc.setMaxFileSize(Long.MAX_VALUE);
LOG.info("Setting the split policy for the Index Table " + indexTableName
+ " as ConstantSizeRegionSplitPolicy with maxFileSize as " + Long.MAX_VALUE + '.');
HRegionInfo[] newRegions = getHRegionInfos(indexTableDesc, splitKeys);
new CreateTableHandler(master, master.getMasterFileSystem(), master.getServerManager(),
indexTableDesc, master.getConfiguration(), newRegions, master.getCatalogTracker(),
master.getAssignmentManager()).process();
// Disable the index table so that when we enable the main table both can be enabled
if (disableTable) {
new DisableTableHandler(master, Bytes.toBytes(indexTableName), master.getCatalogTracker(),
master.getAssignmentManager(), false).process();
}
LOG.info("Created secondary index table " + indexTableName + " for table "
+ iDesc.getNameAsString() + '.');
}
private HRegionInfo[] getHRegionInfos(HTableDescriptor hTableDescriptor, byte[][] splitKeys) {
HRegionInfo[] hRegionInfos = null;
if (splitKeys == null || splitKeys.length == 0) {
hRegionInfos = new HRegionInfo[] { new HRegionInfo(hTableDescriptor.getName(), null, null) };
} else {
int numRegions = splitKeys.length + 1;
hRegionInfos = new HRegionInfo[numRegions];
byte[] startKey = null;
byte[] endKey = null;
for (int i = 0; i < numRegions; i++) {
endKey = (i == splitKeys.length) ? null : splitKeys[i];
hRegionInfos[i] = new HRegionInfo(hTableDescriptor.getName(), startKey, endKey);
startKey = endKey;
}
}
return hRegionInfos;
}
@Override
public void preAssign(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo hri)
throws IOException {
boolean isRegionInTransition = checkRegionInTransition(ctx, hri);
if (isRegionInTransition) {
LOG.info("Not calling assign for region " + hri.getRegionNameAsString()
+ "because the region is already in transition.");
ctx.bypass();
return;
}
}
@Override
public void postAssign(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo regionInfo)
throws IOException {
LOG.info("Entering into postAssign of region " + regionInfo.getRegionNameAsString() + '.');
if (false == regionInfo.getTableNameAsString().endsWith(Constants.INDEX_TABLE_SUFFIX)) {
MasterServices master = ctx.getEnvironment().getMasterServices();
AssignmentManager am = master.getAssignmentManager();
// waiting until user region is removed from transition.
long timeout =
master.getConfiguration()
.getLong("hbase.bulk.assignment.waiton.empty.rit", 5 * 60 * 1000);
Set<HRegionInfo> regionSet = new HashSet<HRegionInfo>(1);
regionSet.add(regionInfo);
try {
am.waitUntilNoRegionsInTransition(timeout, regionSet);
am.waitForAssignment(regionInfo, timeout);
} catch (InterruptedException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Interrupted while region in assignment.");
}
}
ServerName sn = am.getRegionServerOfRegion(regionInfo);
String indexTableName = IndexUtils.getIndexTableName(regionInfo.getTableNameAsString());
List<HRegionInfo> tableRegions = am.getRegionsOfTable(Bytes.toBytes(indexTableName));
for (HRegionInfo hRegionInfo : tableRegions) {
if (0 == Bytes.compareTo(hRegionInfo.getStartKey(), regionInfo.getStartKey())) {
am.addPlan(hRegionInfo.getEncodedName(), new RegionPlan(hRegionInfo, null, sn));
LOG.info("Assigning region " + hRegionInfo.getRegionNameAsString() + " to server " + sn
+ '.');
am.assign(hRegionInfo, true, false, false);
}
}
}
LOG.info("Exiting from postAssign " + regionInfo.getRegionNameAsString() + '.');
}
@Override
public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo hri,
boolean force) throws IOException {
boolean isRegionInTransition = checkRegionInTransition(ctx, hri);
if (isRegionInTransition) {
LOG.info("Not calling move for region because region" + hri.getRegionNameAsString()
+ " is already in transition.");
ctx.bypass();
return;
}
}
@Override
public void preMove(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo hri,
ServerName srcServer, ServerName destServer) throws IOException {
boolean isRegionInTransition = checkRegionInTransition(ctx, hri);
if (isRegionInTransition) {
LOG.info("Not calling move for region " + hri.getRegionNameAsString()
+ "because the region is already in transition.");
ctx.bypass();
return;
}
}
// This is just an additional precaution. This cannot ensure 100% that the RIT regions
// will not be picked up.
// Because the RIT map that is taken here is the copy of original RIT map and there is
// no sync mechanism also.
private boolean checkRegionInTransition(ObserverContext<MasterCoprocessorEnvironment> ctx,
HRegionInfo hri) {
MasterServices master = ctx.getEnvironment().getMasterServices();
AssignmentManager am = master.getAssignmentManager();
boolean isRegionInTransition = false;
String tableName = hri.getTableNameAsString();
if (false == IndexUtils.isIndexTable(tableName)) {
NavigableMap<String, RegionState> regionsInTransition = am.getRegionsInTransition();
RegionState regionState = regionsInTransition.get(hri.getEncodedName());
if (regionState != null) {
isRegionInTransition = true;
} else {
String indexTableName = IndexUtils.getIndexTableName(tableName);
for (Entry<String, RegionState> region : regionsInTransition.entrySet()) {
HRegionInfo regionInfo = region.getValue().getRegion();
if (indexTableName.equals(regionInfo.getTableNameAsString())) {
if (Bytes.compareTo(hri.getStartKey(), regionInfo.getStartKey()) == 0) {
isRegionInTransition = true;
break;
}
}
}
}
}
return isRegionInTransition;
}
@Override
public void postMove(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo regionInfo,
ServerName srcServer, ServerName destServer) throws IOException {
LOG.info("Entering into postMove " + regionInfo.getRegionNameAsString() + '.');
if (false == regionInfo.getTableNameAsString().endsWith(Constants.INDEX_TABLE_SUFFIX)) {
MasterServices master = ctx.getEnvironment().getMasterServices();
AssignmentManager am = master.getAssignmentManager();
// waiting until user region is removed from transition.
long timeout =
master.getConfiguration()
.getLong("hbase.bulk.assignment.waiton.empty.rit", 5 * 60 * 1000);
Set<HRegionInfo> regionSet = new HashSet<HRegionInfo>(1);
regionSet.add(regionInfo);
try {
am.waitUntilNoRegionsInTransition(timeout, regionSet);
am.waitForAssignment(regionInfo, timeout);
destServer = am.getRegionServerOfRegion(regionInfo);
am.putRegionPlan(regionInfo, destServer);
} catch (InterruptedException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Interrupted while region in assignment.");
}
}
String indexTableName = IndexUtils.getIndexTableName(regionInfo.getTableNameAsString());
List<HRegionInfo> tableRegions = am.getRegionsOfTable(Bytes.toBytes(indexTableName));
for (HRegionInfo indexRegionInfo : tableRegions) {
if (0 == Bytes.compareTo(indexRegionInfo.getStartKey(), regionInfo.getStartKey())) {
LOG.info("Assigning region " + indexRegionInfo.getRegionNameAsString() + "from "
+ srcServer + " to server " + destServer + '.');
am.putRegionPlan(indexRegionInfo, destServer);
am.addPlan(indexRegionInfo.getEncodedName(), new RegionPlan(indexRegionInfo, null,
destServer));
am.unassign(indexRegionInfo);
/*
* ((HMaster) master).move(indexRegionInfo.getEncodedNameAsBytes(),
* Bytes.toBytes(destServer.getServerName()));
*/
}
}
}
LOG.info("Exiting from postMove " + regionInfo.getRegionNameAsString() + '.');
}
@Override
public void postDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
byte[] tableName) throws IOException {
LOG.info("Entered into postDisableTableHandler of table " + Bytes.toString(tableName));
MasterServices master = ctx.getEnvironment().getMasterServices();
AssignmentManager am = master.getAssignmentManager();
try {
if (false == IndexUtils.isIndexTable(Bytes.toString(tableName))) {
String indexTableName = IndexUtils.getIndexTableName(tableName);
// Index table may not present following three cases.
// 1) Index details are not specified during table creation then index table wont be
// created.
// 2) Even we specify index details if master restarted in the middle of user table creation
// corresponding index table wont be created. But without creating index table user table
// wont
// be disabled. No need to call disable for index table at that time.
// 3) Index table may be deleted but this wont happen without deleting user table.
if (true == am.getZKTable().isTablePresent(indexTableName)) {
long timeout =
master.getConfiguration().getLong("hbase.bulk.assignment.waiton.empty.rit",
5 * 60 * 1000);
// Both user table and index table should not be in enabling/disabling state at a time.
// If disable is progress for user table then index table should be in ENABLED state.
// If enable is progress for index table wait until table enabled.
waitUntilTableEnabled(timeout, indexTableName, am.getZKTable());
if (waitUntilTableEnabled(timeout, indexTableName, am.getZKTable())) {
new DisableTableHandler(master, Bytes.toBytes(indexTableName),
master.getCatalogTracker(), am, false).process();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Table " + indexTableName + " not in ENABLED state to disable.");
}
}
}
}
} finally {
// clear user table region plans in secondary index load balancer.
clearRegionPlans((HMaster) master, Bytes.toString(tableName));
}
LOG.info("Exiting from postDisableTableHandler of table " + Bytes.toString(tableName));
}
private void clearRegionPlans(HMaster master, String tableName) {
((SecIndexLoadBalancer) master.getBalancer()).clearTableRegionPlans(tableName);
}
@Override
public void postEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
byte[] tableName) throws IOException {
LOG.info("Entered into postEnableTableHandler of table " + Bytes.toString(tableName));
if (false == IndexUtils.isIndexTable(Bytes.toString(tableName))) {
MasterServices master = ctx.getEnvironment().getMasterServices();
AssignmentManager am = master.getAssignmentManager();
String indexTableName = IndexUtils.getIndexTableName(tableName);
// Index table may not present in three cases
// 1) Index details are not specified during table creation then index table wont be created.
// 2) Even we specify index details if master restarted in the middle of user table creation
// corresponding index table wont be created. Then no need to call enable for index table
// because it will be created as part of preMasterInitialization and enable.
// 3) Index table may be deleted but this wont happen without deleting user table.
if (true == am.getZKTable().isTablePresent(indexTableName)) {
long timeout =
master.getConfiguration().getLong("hbase.bulk.assignment.waiton.empty.rit",
5 * 60 * 1000);
// Both user table and index table should not be in enabling/disabling state at a time.
// If enable is progress for user table then index table should be in disabled state.
// If disable is progress for index table wait until table disabled.
if (waitUntilTableDisabled(timeout, indexTableName, am.getZKTable())) {
new EnableTableHandler(master, Bytes.toBytes(indexTableName), master.getCatalogTracker(),
am, false).process();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Table " + indexTableName + " not in DISABLED state to enable.");
}
}
}
}
LOG.info("Exiting from postEnableTableHandler of table " + Bytes.toString(tableName));
}
private boolean waitUntilTableDisabled(long timeout, String tableName, ZKTable zk) {
long startTime = System.currentTimeMillis();
long remaining = timeout;
boolean disabled = false;
while (!(disabled = zk.isDisabledTable(tableName)) && remaining > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Interrupted while waiting for table" + tableName + " set to DISABLED.");
}
}
remaining = timeout - (System.currentTimeMillis() - startTime);
}
if (remaining <= 0) {
return disabled;
} else {
return true;
}
}
private boolean waitUntilTableEnabled(long timeout, String tableName, ZKTable zk) {
long startTime = System.currentTimeMillis();
long remaining = timeout;
boolean enabled = false;
while (!(enabled = zk.isEnabledTable(tableName)) && remaining > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Interrupted while waiting for table " + tableName + "state set to ENABLED.");
}
}
remaining = timeout - (System.currentTimeMillis() - startTime);
}
if (remaining <= 0) {
return enabled;
} else {
return true;
}
}
@Override
public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
byte[] tableName) throws IOException {
LOG.info("Entered into postDeleteTableHandler of table " + Bytes.toString(tableName) + '.');
MasterServices master = ctx.getEnvironment().getMasterServices();
String indexTableName = IndexUtils.getIndexTableName(tableName);
boolean indexTablePresent =
master.getAssignmentManager().getZKTable().isTablePresent(indexTableName);
// Not checking for disabled state because before deleting user table both user and index table
// should be disabled.
if ((false == IndexUtils.isIndexTable(Bytes.toString(tableName))) && indexTablePresent) {
new DeleteTableHandler(Bytes.toBytes(indexTableName), master, master).process();
}
LOG.info("Exiting from postDeleteTableHandler of table " + Bytes.toString(tableName) + '.');
}
@Override
public void preMasterInitialization(ObserverContext<MasterCoprocessorEnvironment> ctx)
throws IOException {
LOG.info("Entering into preMasterInitialization.");
MasterServices master = ctx.getEnvironment().getMasterServices();
AssignmentManager am = master.getAssignmentManager();
ZKTable zkTable = am.getZKTable();
long timeout =
master.getConfiguration().getLong("hbase.bulk.assignment.waiton.empty.rit", 5 * 60 * 1000);
try {
am.waitUntilNoRegionsInTransition(timeout);
} catch (InterruptedException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Interrupted while waiting for the regions in transition to complete.", e);
}
}
TableDescriptors tableDescriptors = master.getTableDescriptors();
Map<String, HTableDescriptor> descMap = tableDescriptors.getAll();
Collection<HTableDescriptor> htds = descMap.values();
IndexedHTableDescriptor iHtd = null;
Configuration conf = master.getConfiguration();
FileSystem fs = FSUtils.getCurrentFileSystem(conf);
Path rootPath = FSUtils.getRootDir(conf);
for (HTableDescriptor htd : htds) {
if (false == htd.getNameAsString().endsWith(Constants.INDEX_TABLE_SUFFIX)) {
FSDataInputStream fsDataInputStream = null;
try {
Path path = FSUtils.getTablePath(rootPath, htd.getName());
FileStatus status = getTableInfoPath(fs, path);
if (null == status) {
return;
}
fsDataInputStream = fs.open(status.getPath());
iHtd = new IndexedHTableDescriptor();
iHtd.readFields(fsDataInputStream);
} catch (EOFException e) {
if (LOG.isDebugEnabled()) {
LOG.debug(iHtd.getNameAsString() + " is normal table and not an indexed table.", e);
}
} catch (IOException i) {
throw i;
} finally {
if (null != fsDataInputStream) {
fsDataInputStream.close();
}
}
if (false == iHtd.getIndices().isEmpty()) {
String tableName = iHtd.getNameAsString();
String indexTableName = IndexUtils.getIndexTableName(tableName);
boolean tableExists = MetaReader.tableExists(master.getCatalogTracker(), tableName);
boolean indexTableExists =
MetaReader.tableExists(master.getCatalogTracker(), indexTableName);
if ((true == tableExists) && (false == indexTableExists)) {
LOG.info("Table has index specification details but " + "no corresponding index table.");
List<HRegionInfo> regions =
MetaReader.getTableRegions(master.getCatalogTracker(), iHtd.getName());
HRegionInfo[] regionsArray = new HRegionInfo[regions.size()];
byte[][] splitKeys = IndexUtils.getSplitKeys(regions.toArray(regionsArray));
createSecondaryIndexTable(iHtd, splitKeys, master, false);
} else if (true == tableExists && true == indexTableExists) {
// If both tables are present both should be in same state in zookeeper. If tables are
// partially enabled or disabled they will be processed as part of recovery
// enabling/disabling tables.
// if user table is in ENABLED state and index table is in DISABLED state means master
// restarted as soon as user table enabled. So here we need to enable index table.
if (zkTable.isEnabledTable(tableName) && zkTable.isDisabledTable(indexTableName)) {
new EnableTableHandler(master, Bytes.toBytes(indexTableName),
master.getCatalogTracker(), am, false).process();
} else if (zkTable.isDisabledTable(tableName) && zkTable.isEnabledTable(indexTableName)) {
// If user table is in DISABLED state and index table is in ENABLED state means master
// restarted as soon as user table disabled. So here we need to disable index table.
new DisableTableHandler(master, Bytes.toBytes(indexTableName),
master.getCatalogTracker(), am, false).process();
// clear index table region plans in secondary index load balancer.
clearRegionPlans((HMaster) master, indexTableName);
}
}
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Balancing after master initialization.");
}
try {
master.getAssignmentManager().waitUntilNoRegionsInTransition(timeout);
} catch (InterruptedException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Interrupted while waiting for the regions in transition to complete.", e);
}
}
((HMaster) master).balanceInternals();
LOG.info("Exiting from preMasterInitialization.");
}
public static FileStatus getTableInfoPath(final FileSystem fs, final Path tabledir)
throws IOException {
FileStatus[] status = FSUtils.listStatus(fs, tabledir, new PathFilter() {
@Override
public boolean accept(Path p) {
// Accept any file that starts with TABLEINFO_NAME
return p.getName().startsWith(FSTableDescriptors.TABLEINFO_NAME);
}
});
if (status == null || status.length < 1) return null;
Arrays.sort(status, new FileStatusFileNameComparator());
if (status.length > 1) {
// Clean away old versions of .tableinfo
for (int i = 1; i < status.length; i++) {
Path p = status[i].getPath();
// Clean up old versions
if (!fs.delete(p, false)) {
LOG.warn("Failed cleanup of " + p);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaned up old tableinfo file " + p);
}
}
}
}
return status[0];
}
/**
* Compare {@link FileStatus} instances by {@link Path#getName()}. Returns in reverse order.
*/
private static class FileStatusFileNameComparator implements Comparator<FileStatus> {
@Override
public int compare(FileStatus left, FileStatus right) {
return -left.compareTo(right);
}
}
}