@Test(timeout = 60000)
  public void testModifyTableDeleteCF() throws Exception {
    final TableName tableName = TableName.valueOf("testModifyTableDeleteCF");
    final String cf1 = "cf1";
    final String cf2 = "cf2";
    final String cf3 = "cf3";
    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();

    MasterProcedureTestingUtility.createTable(procExec, tableName, null, cf1, cf2, cf3);
    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertEquals(3, currentHtd.getFamiliesKeys().size());

    // Test 1: Modify the table descriptor
    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
    htd.removeFamily(cf2.getBytes());

    long procId =
        ProcedureTestingUtility.submitAndWait(
            procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd));
    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));

    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertEquals(2, currentHtd.getFamiliesKeys().size());
    assertFalse(currentHtd.hasFamily(cf2.getBytes()));

    // Test 2: Modify the table descriptor offline
    UTIL.getAdmin().disableTable(tableName);
    ProcedureTestingUtility.waitNoProcedureRunning(procExec);

    HTableDescriptor htd2 = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
    htd2.removeFamily(cf3.getBytes());
    // Disable Sanity check
    htd2.setConfiguration("hbase.table.sanity.checks", Boolean.FALSE.toString());

    long procId2 =
        ProcedureTestingUtility.submitAndWait(
            procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd2));
    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));

    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertEquals(1, currentHtd.getFamiliesKeys().size());
    assertFalse(currentHtd.hasFamily(cf3.getBytes()));

    // Removing the last family will fail
    HTableDescriptor htd3 = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
    htd3.removeFamily(cf1.getBytes());
    long procId3 =
        ProcedureTestingUtility.submitAndWait(
            procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd3));
    final ProcedureInfo result = procExec.getResult(procId3);
    assertEquals(true, result.isFailed());
    Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
    assertTrue(
        "expected DoNotRetryIOException, got " + cause, cause instanceof DoNotRetryIOException);
    assertEquals(1, currentHtd.getFamiliesKeys().size());
    assertTrue(currentHtd.hasFamily(cf1.getBytes()));
  }
  /*
   * Take a snapshot of a table, add metadata, and verify that this only
   * affects one table
   * @param online - Whether the table is online or not during the snapshot
   */
  private void runTestSnapshotMetadataChangesIndependent(boolean online) throws Exception {
    FileSystem fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
    Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();

    // Create a table
    Admin admin = UTIL.getHBaseAdmin();
    final long startTime = System.currentTimeMillis();
    final TableName localTableName = TableName.valueOf(STRING_TABLE_NAME + startTime);
    Table original = UTIL.createTable(localTableName, TEST_FAM);
    UTIL.loadTable(original, TEST_FAM);

    final String snapshotNameAsString = "snapshot_" + localTableName;

    // Create a snapshot
    SnapshotTestingUtils.createSnapshotAndValidate(
        admin, localTableName, TEST_FAM_STR, snapshotNameAsString, rootDir, fs, online);

    if (!online) {
      admin.enableTable(localTableName);
    }
    TableName cloneTableName = TableName.valueOf("test-clone-" + localTableName);

    // Clone the snapshot
    byte[] snapshotName = Bytes.toBytes(snapshotNameAsString);
    admin.cloneSnapshot(snapshotName, cloneTableName);

    // Add a new column family to the original table
    byte[] TEST_FAM_2 = Bytes.toBytes("fam2");
    HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAM_2);

    admin.disableTable(localTableName);
    admin.addColumnFamily(localTableName, hcd);

    // Verify that it is not in the snapshot
    admin.enableTable(localTableName);

    // get a description of the cloned table
    // get a list of its families
    // assert that the family is there
    HTableDescriptor originalTableDescriptor = original.getTableDescriptor();
    HTableDescriptor clonedTableDescriptor = admin.getTableDescriptor(cloneTableName);

    Assert.assertTrue(
        "The original family was not found. There is something wrong. ",
        originalTableDescriptor.hasFamily(TEST_FAM));
    Assert.assertTrue(
        "The original family was not found in the clone. There is something wrong. ",
        clonedTableDescriptor.hasFamily(TEST_FAM));

    Assert.assertTrue(
        "The new family was not found. ", originalTableDescriptor.hasFamily(TEST_FAM_2));
    Assert.assertTrue(
        "The new family was not found. ", !clonedTableDescriptor.hasFamily(TEST_FAM_2));
  }
  @Test(timeout = 60000)
  public void testModifyTableAddCF() throws Exception {
    final TableName tableName = TableName.valueOf("testModifyTableAddCF");
    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();

    MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1");
    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertEquals(1, currentHtd.getFamiliesKeys().size());

    // Test 1: Modify the table descriptor online
    String cf2 = "cf2";
    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
    htd.addFamily(new HColumnDescriptor(cf2));

    long procId =
        ProcedureTestingUtility.submitAndWait(
            procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd));
    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));

    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertEquals(2, currentHtd.getFamiliesKeys().size());
    assertTrue(currentHtd.hasFamily(cf2.getBytes()));

    // Test 2: Modify the table descriptor offline
    UTIL.getAdmin().disableTable(tableName);
    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
    String cf3 = "cf3";
    HTableDescriptor htd2 = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
    htd2.addFamily(new HColumnDescriptor(cf3));

    long procId2 =
        ProcedureTestingUtility.submitAndWait(
            procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd2));
    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));

    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertTrue(currentHtd.hasFamily(cf3.getBytes()));
    assertEquals(3, currentHtd.getFamiliesKeys().size());
  }
  @Test(timeout = 60000)
  public void testRecoveryAndDoubleExecutionOnline() throws Exception {
    final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecutionOnline");
    final String cf2 = "cf2";
    final String cf3 = "cf3";
    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();

    // create the table
    HRegionInfo[] regions =
        MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3);

    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);

    // Modify multiple properties of the table.
    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
    boolean newCompactionEnableOption = htd.isCompactionEnabled() ? false : true;
    htd.setCompactionEnabled(newCompactionEnableOption);
    htd.addFamily(new HColumnDescriptor(cf2));
    htd.removeFamily(cf3.getBytes());

    // Start the Modify procedure && kill the executor
    long procId =
        procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), htd));

    // Restart the executor and execute the step twice
    int numberOfSteps = ModifyTableState.values().length;
    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, numberOfSteps);

    // Validate descriptor
    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
    assertEquals(newCompactionEnableOption, currentHtd.isCompactionEnabled());
    assertEquals(2, currentHtd.getFamiliesKeys().size());
    assertTrue(currentHtd.hasFamily(cf2.getBytes()));
    assertFalse(currentHtd.hasFamily(cf3.getBytes()));

    // cf2 should be added cf3 should be removed
    MasterProcedureTestingUtility.validateTableCreation(
        UTIL.getHBaseCluster().getMaster(), tableName, regions, "cf1", cf2);
  }
 private void verifyColumns() {
   if (AbstractRecordReader.isStarQuery(columns)) {
     return;
   }
   for (SchemaPath column : columns) {
     if (!(column.equals(ROW_KEY_PATH)
         || hTableDesc.hasFamily(HBaseUtils.getBytes(column.getRootSegment().getPath())))) {
       DrillRuntimeException.format(
           "The column family '%s' does not exist in HBase table: %s .",
           column.getRootSegment().getPath(), hTableDesc.getNameAsString());
     }
   }
 }
  /**
   * Action before any real action of modifying column family.
   *
   * @param env MasterProcedureEnv
   * @throws IOException
   */
  private void prepareModify(final MasterProcedureEnv env) throws IOException {
    // Checks whether the table is allowed to be modified.
    MasterDDLOperationHelper.checkTableModifiable(env, tableName);

    unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
    if (unmodifiedHTableDescriptor == null) {
      throw new IOException("HTableDescriptor missing for " + tableName);
    }
    if (!unmodifiedHTableDescriptor.hasFamily(cfDescriptor.getName())) {
      throw new InvalidFamilyOperationException(
          "Family '" + getColumnFamilyName() + "' does not exist, so it cannot be modified");
    }
  }
  /**
   * Modify Column of a table
   * @param tableName
   * @param hcd HColumnDesciptor
   * @return Modified HTableDescriptor with the column modified.
   * @throws IOException
   */
  public HTableDescriptor modifyColumn(byte[] tableName, HColumnDescriptor hcd)
      throws IOException {
    LOG.info("AddModifyColumn. Table = " + Bytes.toString(tableName)
        + " HCD = " + hcd.toString());

    HTableDescriptor htd = this.services.getTableDescriptors().get(tableName);
    byte [] familyName = hcd.getName();
    if(!htd.hasFamily(familyName)) {
      throw new InvalidFamilyOperationException("Family '" +
        Bytes.toString(familyName) + "' doesn't exists so cannot be modified");
    }
    htd.addFamily(hcd);
    this.services.getTableDescriptors().add(htd);
    return htd;
  }
  /** Add the column family to the file system */
  private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
    // Update table descriptor
    LOG.info("AddColumn. Table = " + tableName + " HCD = " + cfDescriptor.toString());

    HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);

    if (htd.hasFamily(cfDescriptor.getName())) {
      // It is possible to reach this situation, as we could already add the column family
      // to table descriptor, but the master failover happens before we complete this state.
      // We should be able to handle running this function multiple times without causing problem.
      return;
    }

    htd.addFamily(cfDescriptor);
    env.getMasterServices().getTableDescriptors().add(htd);
  }
  /**
   * Restore the table descriptor back to pre-add
   *
   * @param env MasterProcedureEnv
   * @throws IOException
   */
  private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
    HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
    if (htd.hasFamily(cfDescriptor.getName())) {
      // Remove the column family from file system and update the table descriptor to
      // the before-add-column-family-state
      MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(
          env,
          tableName,
          getRegionInfoList(env),
          cfDescriptor.getName(),
          cfDescriptor.isMobEnabled());

      env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);

      // Make sure regions are opened after table descriptor is updated.
      reOpenAllRegionsIfTableIsOnline(env);
    }
  }
  /**
   * Action before any real action of adding column family.
   *
   * @param env MasterProcedureEnv
   * @throws IOException
   */
  private void prepareAdd(final MasterProcedureEnv env) throws IOException {
    // Checks whether the table is allowed to be modified.
    MasterDDLOperationHelper.checkTableModifiable(env, tableName);

    // In order to update the descriptor, we need to retrieve the old descriptor for comparison.
    unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
    if (unmodifiedHTableDescriptor == null) {
      throw new IOException("HTableDescriptor missing for " + tableName);
    }
    if (unmodifiedHTableDescriptor.hasFamily(cfDescriptor.getName())) {
      throw new InvalidFamilyOperationException(
          "Column family '"
              + getColumnFamilyName()
              + "' in table '"
              + tableName
              + "' already exists so cannot be added");
    }
  }