org.apache.hadoop.hbase.master.procedure.CreateTableProcedure.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.master.procedure.CreateTableProcedure.java

Source

/**
 * 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.procedure;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.TableDescriptor;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.RegionReplicaUtil;
import org.apache.hadoop.hbase.client.TableState;
import org.apache.hadoop.hbase.master.AssignmentManager;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.master.MasterFileSystem;
import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.CreateTableState;
import org.apache.hadoop.hbase.util.FSTableDescriptors;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.ModifyRegionUtils;
import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
import org.apache.hadoop.security.UserGroupInformation;

import com.google.common.collect.Lists;

@InterfaceAudience.Private
public class CreateTableProcedure extends StateMachineProcedure<MasterProcedureEnv, CreateTableState>
        implements TableProcedureInterface {
    private static final Log LOG = LogFactory.getLog(CreateTableProcedure.class);

    private final AtomicBoolean aborted = new AtomicBoolean(false);

    // used for compatibility with old clients
    private final ProcedurePrepareLatch syncLatch;

    private HTableDescriptor hTableDescriptor;
    private List<HRegionInfo> newRegions;
    private UserGroupInformation user;

    public CreateTableProcedure() {
        // Required by the Procedure framework to create the procedure on replay
        syncLatch = null;
    }

    public CreateTableProcedure(final MasterProcedureEnv env, final HTableDescriptor hTableDescriptor,
            final HRegionInfo[] newRegions) throws IOException {
        this(env, hTableDescriptor, newRegions, null);
    }

    public CreateTableProcedure(final MasterProcedureEnv env, final HTableDescriptor hTableDescriptor,
            final HRegionInfo[] newRegions, final ProcedurePrepareLatch syncLatch) throws IOException {
        this.hTableDescriptor = hTableDescriptor;
        this.newRegions = newRegions != null ? Lists.newArrayList(newRegions) : null;
        this.user = env.getRequestUser().getUGI();

        // used for compatibility with clients without procedures
        // they need a sync TableExistsException
        this.syncLatch = syncLatch;
    }

    @Override
    protected Flow executeFromState(final MasterProcedureEnv env, final CreateTableState state)
            throws InterruptedException {
        if (LOG.isTraceEnabled()) {
            LOG.trace(this + " execute state=" + state);
        }
        try {
            switch (state) {
            case CREATE_TABLE_PRE_OPERATION:
                // Verify if we can create the table
                boolean exists = !prepareCreate(env);
                ProcedurePrepareLatch.releaseLatch(syncLatch, this);

                if (exists) {
                    assert isFailed() : "the delete should have an exception here";
                    return Flow.NO_MORE_STATE;
                }

                preCreate(env);
                setNextState(CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT);
                break;
            case CREATE_TABLE_WRITE_FS_LAYOUT:
                newRegions = createFsLayout(env, hTableDescriptor, newRegions);
                setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META);
                break;
            case CREATE_TABLE_ADD_TO_META:
                newRegions = addTableToMeta(env, hTableDescriptor, newRegions);
                setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS);
                break;
            case CREATE_TABLE_ASSIGN_REGIONS:
                assignRegions(env, getTableName(), newRegions);
                setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE);
                break;
            case CREATE_TABLE_UPDATE_DESC_CACHE:
                updateTableDescCache(env, getTableName());
                setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION);
                break;
            case CREATE_TABLE_POST_OPERATION:
                postCreate(env);
                return Flow.NO_MORE_STATE;
            default:
                throw new UnsupportedOperationException("unhandled state=" + state);
            }
        } catch (IOException e) {
            LOG.error("Error trying to create table=" + getTableName() + " state=" + state, e);
            setFailure("master-create-table", e);
        }
        return Flow.HAS_MORE_STATE;
    }

    @Override
    protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state) throws IOException {
        if (LOG.isTraceEnabled()) {
            LOG.trace(this + " rollback state=" + state);
        }
        try {
            switch (state) {
            case CREATE_TABLE_POST_OPERATION:
                break;
            case CREATE_TABLE_UPDATE_DESC_CACHE:
                DeleteTableProcedure.deleteTableDescriptorCache(env, getTableName());
                break;
            case CREATE_TABLE_ASSIGN_REGIONS:
                DeleteTableProcedure.deleteAssignmentState(env, getTableName());
                break;
            case CREATE_TABLE_ADD_TO_META:
                DeleteTableProcedure.deleteFromMeta(env, getTableName(), newRegions);
                break;
            case CREATE_TABLE_WRITE_FS_LAYOUT:
                DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, false);
                break;
            case CREATE_TABLE_PRE_OPERATION:
                DeleteTableProcedure.deleteTableStates(env, getTableName());
                // TODO-MAYBE: call the deleteTable coprocessor event?
                ProcedurePrepareLatch.releaseLatch(syncLatch, this);
                break;
            default:
                throw new UnsupportedOperationException("unhandled state=" + state);
            }
        } catch (IOException e) {
            // This will be retried. Unless there is a bug in the code,
            // this should be just a "temporary error" (e.g. network down)
            LOG.warn("Failed rollback attempt step=" + state + " table=" + getTableName(), e);
            throw e;
        }
    }

    @Override
    protected CreateTableState getState(final int stateId) {
        return CreateTableState.valueOf(stateId);
    }

    @Override
    protected int getStateId(final CreateTableState state) {
        return state.getNumber();
    }

    @Override
    protected CreateTableState getInitialState() {
        return CreateTableState.CREATE_TABLE_PRE_OPERATION;
    }

    @Override
    protected void setNextState(final CreateTableState state) {
        if (aborted.get()) {
            setAbortFailure("create-table", "abort requested");
        } else {
            super.setNextState(state);
        }
    }

    @Override
    public TableName getTableName() {
        return hTableDescriptor.getTableName();
    }

    @Override
    public TableOperationType getTableOperationType() {
        return TableOperationType.CREATE;
    }

    @Override
    public boolean abort(final MasterProcedureEnv env) {
        aborted.set(true);
        return true;
    }

    @Override
    public void toStringClassDetails(StringBuilder sb) {
        sb.append(getClass().getSimpleName());
        sb.append(" (table=");
        sb.append(getTableName());
        sb.append(") user=");
        sb.append(user);
    }

    @Override
    public void serializeStateData(final OutputStream stream) throws IOException {
        super.serializeStateData(stream);

        MasterProcedureProtos.CreateTableStateData.Builder state = MasterProcedureProtos.CreateTableStateData
                .newBuilder().setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
                .setTableSchema(hTableDescriptor.convert());
        if (newRegions != null) {
            for (HRegionInfo hri : newRegions) {
                state.addRegionInfo(HRegionInfo.convert(hri));
            }
        }
        state.build().writeDelimitedTo(stream);
    }

    @Override
    public void deserializeStateData(final InputStream stream) throws IOException {
        super.deserializeStateData(stream);

        MasterProcedureProtos.CreateTableStateData state = MasterProcedureProtos.CreateTableStateData
                .parseDelimitedFrom(stream);
        user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
        hTableDescriptor = HTableDescriptor.convert(state.getTableSchema());
        if (state.getRegionInfoCount() == 0) {
            newRegions = null;
        } else {
            newRegions = new ArrayList<HRegionInfo>(state.getRegionInfoCount());
            for (HBaseProtos.RegionInfo hri : state.getRegionInfoList()) {
                newRegions.add(HRegionInfo.convert(hri));
            }
        }
    }

    @Override
    protected boolean acquireLock(final MasterProcedureEnv env) {
        if (!env.isInitialized() && !getTableName().isSystemTable()) {
            return false;
        }
        return env.getProcedureQueue().tryAcquireTableExclusiveLock(getTableName(), "create table");
    }

    @Override
    protected void releaseLock(final MasterProcedureEnv env) {
        env.getProcedureQueue().releaseTableExclusiveLock(getTableName());
    }

    private boolean prepareCreate(final MasterProcedureEnv env) throws IOException {
        final TableName tableName = getTableName();
        if (MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
            setFailure("master-create-table", new TableExistsException(getTableName()));
            return false;
        }
        return true;
    }

    private void preCreate(final MasterProcedureEnv env) throws IOException, InterruptedException {
        if (!getTableName().isSystemTable()) {
            ProcedureSyncWait.getMasterQuotaManager(env).checkNamespaceTableAndRegionQuota(getTableName(),
                    newRegions.size());
        }

        final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
        if (cpHost != null) {
            final HRegionInfo[] regions = newRegions == null ? null
                    : newRegions.toArray(new HRegionInfo[newRegions.size()]);
            user.doAs(new PrivilegedExceptionAction<Void>() {
                @Override
                public Void run() throws Exception {
                    cpHost.preCreateTableHandler(hTableDescriptor, regions);
                    return null;
                }
            });
        }
    }

    private void postCreate(final MasterProcedureEnv env) throws IOException, InterruptedException {
        final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
        if (cpHost != null) {
            final HRegionInfo[] regions = (newRegions == null) ? null
                    : newRegions.toArray(new HRegionInfo[newRegions.size()]);
            user.doAs(new PrivilegedExceptionAction<Void>() {
                @Override
                public Void run() throws Exception {
                    cpHost.postCreateTableHandler(hTableDescriptor, regions);
                    return null;
                }
            });
        }
    }

    protected interface CreateHdfsRegions {
        List<HRegionInfo> createHdfsRegions(final MasterProcedureEnv env, final Path tableRootDir,
                final TableName tableName, final List<HRegionInfo> newRegions) throws IOException;
    }

    protected static List<HRegionInfo> createFsLayout(final MasterProcedureEnv env,
            final HTableDescriptor hTableDescriptor, final List<HRegionInfo> newRegions) throws IOException {
        return createFsLayout(env, hTableDescriptor, newRegions, new CreateHdfsRegions() {
            @Override
            public List<HRegionInfo> createHdfsRegions(final MasterProcedureEnv env, final Path tableRootDir,
                    final TableName tableName, final List<HRegionInfo> newRegions) throws IOException {
                HRegionInfo[] regions = newRegions != null ? newRegions.toArray(new HRegionInfo[newRegions.size()])
                        : null;
                return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), tableRootDir, hTableDescriptor,
                        regions, null);
            }
        });
    }

    protected static List<HRegionInfo> createFsLayout(final MasterProcedureEnv env,
            final HTableDescriptor hTableDescriptor, List<HRegionInfo> newRegions,
            final CreateHdfsRegions hdfsRegionHandler) throws IOException {
        final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
        final Path tempdir = mfs.getTempDir();

        // 1. Create Table Descriptor
        // using a copy of descriptor, table will be created enabling first
        TableDescriptor underConstruction = new TableDescriptor(hTableDescriptor);
        final Path tempTableDir = FSUtils.getTableDir(tempdir, hTableDescriptor.getTableName());
        ((FSTableDescriptors) (env.getMasterServices().getTableDescriptors()))
                .createTableDescriptorForTableDirectory(tempTableDir, underConstruction, false);

        // 2. Create Regions
        newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir, hTableDescriptor.getTableName(), newRegions);

        // 3. Move Table temp directory to the hbase root location
        final Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), hTableDescriptor.getTableName());
        FileSystem fs = mfs.getFileSystem();
        if (!fs.delete(tableDir, true) && fs.exists(tableDir)) {
            throw new IOException("Couldn't delete " + tableDir);
        }
        if (!fs.rename(tempTableDir, tableDir)) {
            throw new IOException("Unable to move table from temp=" + tempTableDir + " to hbase root=" + tableDir);
        }
        return newRegions;
    }

    protected static List<HRegionInfo> addTableToMeta(final MasterProcedureEnv env,
            final HTableDescriptor hTableDescriptor, final List<HRegionInfo> regions) throws IOException {
        if (regions != null && regions.size() > 0) {
            ProcedureSyncWait.waitMetaRegions(env);

            // Add regions to META
            addRegionsToMeta(env, hTableDescriptor, regions);
            // Add replicas if needed
            List<HRegionInfo> newRegions = addReplicas(env, hTableDescriptor, regions);

            // Setup replication for region replicas if needed
            if (hTableDescriptor.getRegionReplication() > 1) {
                ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration());
            }
            return newRegions;
        }
        return regions;
    }

    /**
     * Create any replicas for the regions (the default replicas that was
     * already created is passed to the method)
     * @param hTableDescriptor descriptor to use
     * @param regions default replicas
     * @return the combined list of default and non-default replicas
     */
    private static List<HRegionInfo> addReplicas(final MasterProcedureEnv env,
            final HTableDescriptor hTableDescriptor, final List<HRegionInfo> regions) {
        int numRegionReplicas = hTableDescriptor.getRegionReplication() - 1;
        if (numRegionReplicas <= 0) {
            return regions;
        }
        List<HRegionInfo> hRegionInfos = new ArrayList<HRegionInfo>((numRegionReplicas + 1) * regions.size());
        for (int i = 0; i < regions.size(); i++) {
            for (int j = 1; j <= numRegionReplicas; j++) {
                hRegionInfos.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(i), j));
            }
        }
        hRegionInfos.addAll(regions);
        return hRegionInfos;
    }

    protected static void assignRegions(final MasterProcedureEnv env, final TableName tableName,
            final List<HRegionInfo> regions) throws IOException {
        ProcedureSyncWait.waitRegionServers(env);

        // Trigger immediate assignment of the regions in round-robin fashion
        final AssignmentManager assignmentManager = env.getMasterServices().getAssignmentManager();
        ModifyRegionUtils.assignRegions(assignmentManager, regions);

        // Enable table
        assignmentManager.getTableStateManager().setTableState(tableName, TableState.State.ENABLED);
    }

    /**
     * Add the specified set of regions to the hbase:meta table.
     */
    protected static void addRegionsToMeta(final MasterProcedureEnv env, final HTableDescriptor hTableDescriptor,
            final List<HRegionInfo> regionInfos) throws IOException {
        MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), regionInfos,
                hTableDescriptor.getRegionReplication());
    }

    protected static void updateTableDescCache(final MasterProcedureEnv env, final TableName tableName)
            throws IOException {
        env.getMasterServices().getTableDescriptors().get(tableName);
    }
}