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.procedure;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.IOException;
026import org.apache.hadoop.fs.FileStatus;
027import org.apache.hadoop.fs.FileSystem;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.fs.PathFilter;
030import org.apache.hadoop.hbase.HBaseTestingUtil;
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.InvalidFamilyOperationException;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.client.Admin;
035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
036import org.apache.hadoop.hbase.client.Table;
037import org.apache.hadoop.hbase.client.TableDescriptor;
038import org.apache.hadoop.hbase.testclassification.MasterTests;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.apache.hadoop.hbase.util.CommonFSUtils;
042import org.apache.hadoop.hbase.wal.WALSplitUtil;
043import org.junit.jupiter.api.AfterAll;
044import org.junit.jupiter.api.AfterEach;
045import org.junit.jupiter.api.BeforeAll;
046import org.junit.jupiter.api.BeforeEach;
047import org.junit.jupiter.api.Tag;
048import org.junit.jupiter.api.Test;
049
050@Tag(MasterTests.TAG)
051@Tag(MediumTests.TAG)
052public class TestDeleteColumnFamilyProcedureFromClient {
053
054  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
055
056  private static final TableName TABLENAME = TableName.valueOf("column_family_handlers");
057  private static final byte[][] FAMILIES =
058    new byte[][] { Bytes.toBytes("cf1"), Bytes.toBytes("cf2"), Bytes.toBytes("cf3") };
059
060  /**
061   * Start up a mini cluster and put a small table of empty regions into it.
062   */
063  @BeforeAll
064  public static void beforeAllTests() throws Exception {
065    TEST_UTIL.startMiniCluster(2);
066  }
067
068  @AfterAll
069  public static void afterAllTests() throws Exception {
070    TEST_UTIL.shutdownMiniCluster();
071  }
072
073  @BeforeEach
074  public void setup() throws IOException, InterruptedException {
075    // Create a table of three families. This will assign a region.
076    TEST_UTIL.createTable(TABLENAME, FAMILIES);
077    Table t = TEST_UTIL.getConnection().getTable(TABLENAME);
078    TEST_UTIL.waitUntilNoRegionsInTransition();
079
080    // Load the table with data for all families
081    TEST_UTIL.loadTable(t, FAMILIES);
082
083    TEST_UTIL.flush();
084
085    t.close();
086
087    TEST_UTIL.ensureSomeRegionServersAvailable(2);
088  }
089
090  @AfterEach
091  public void cleanup() throws Exception {
092    TEST_UTIL.deleteTable(TABLENAME);
093  }
094
095  @Test
096  public void deleteColumnFamilyWithMultipleRegions() throws Exception {
097    Admin admin = TEST_UTIL.getAdmin();
098    TableDescriptor beforehtd = admin.getDescriptor(TABLENAME);
099
100    FileSystem fs = TEST_UTIL.getDFSCluster().getFileSystem();
101
102    // 1 - Check if table exists in descriptor
103    assertTrue(admin.isTableAvailable(TABLENAME));
104
105    // 2 - Check if all three families exist in descriptor
106    assertEquals(3, beforehtd.getColumnFamilyCount());
107    ColumnFamilyDescriptor[] families = beforehtd.getColumnFamilies();
108    for (int i = 0; i < families.length; i++) {
109      assertTrue(families[i].getNameAsString().equals("cf" + (i + 1)));
110    }
111
112    // 3 - Check if table exists in FS
113    Path tableDir = CommonFSUtils.getTableDir(TEST_UTIL.getDefaultRootDirPath(), TABLENAME);
114    assertTrue(fs.exists(tableDir));
115
116    // 4 - Check if all the 3 column families exist in FS
117    FileStatus[] fileStatus = fs.listStatus(tableDir);
118    for (int i = 0; i < fileStatus.length; i++) {
119      if (fileStatus[i].isDirectory() == true) {
120        FileStatus[] cf = fs.listStatus(fileStatus[i].getPath(), new PathFilter() {
121          @Override
122          public boolean accept(Path p) {
123            if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) {
124              return false;
125            }
126            return true;
127          }
128        });
129        int k = 1;
130        for (int j = 0; j < cf.length; j++) {
131          if (cf[j].isDirectory() == true && cf[j].getPath().getName().startsWith(".") == false) {
132            assertEquals(cf[j].getPath().getName(), "cf" + k);
133            k++;
134          }
135        }
136      }
137    }
138
139    // TEST - Disable and delete the column family
140    admin.disableTable(TABLENAME);
141    admin.deleteColumnFamily(TABLENAME, Bytes.toBytes("cf2"));
142
143    // 5 - Check if only 2 column families exist in the descriptor
144    TableDescriptor afterhtd = admin.getDescriptor(TABLENAME);
145    assertEquals(2, afterhtd.getColumnFamilyCount());
146    ColumnFamilyDescriptor[] newFamilies = afterhtd.getColumnFamilies();
147    assertTrue(newFamilies[0].getNameAsString().equals("cf1"));
148    assertTrue(newFamilies[1].getNameAsString().equals("cf3"));
149
150    // 6 - Check if the second column family is gone from the FS
151    fileStatus = fs.listStatus(tableDir);
152    for (int i = 0; i < fileStatus.length; i++) {
153      if (fileStatus[i].isDirectory() == true) {
154        FileStatus[] cf = fs.listStatus(fileStatus[i].getPath(), new PathFilter() {
155          @Override
156          public boolean accept(Path p) {
157            if (WALSplitUtil.isSequenceIdFile(p)) {
158              return false;
159            }
160            return true;
161          }
162        });
163        for (int j = 0; j < cf.length; j++) {
164          if (cf[j].isDirectory() == true) {
165            assertFalse(cf[j].getPath().getName().equals("cf2"));
166          }
167        }
168      }
169    }
170  }
171
172  @Test
173  public void deleteColumnFamilyTwice() throws Exception {
174    Admin admin = TEST_UTIL.getAdmin();
175    TableDescriptor beforehtd = admin.getDescriptor(TABLENAME);
176    String cfToDelete = "cf1";
177
178    FileSystem fs = TEST_UTIL.getDFSCluster().getFileSystem();
179
180    // 1 - Check if table exists in descriptor
181    assertTrue(admin.isTableAvailable(TABLENAME));
182
183    // 2 - Check if all the target column family exist in descriptor
184    ColumnFamilyDescriptor[] families = beforehtd.getColumnFamilies();
185    Boolean foundCF = false;
186    for (int i = 0; i < families.length; i++) {
187      if (families[i].getNameAsString().equals(cfToDelete)) {
188        foundCF = true;
189        break;
190      }
191    }
192    assertTrue(foundCF);
193
194    // 3 - Check if table exists in FS
195    Path tableDir = CommonFSUtils.getTableDir(TEST_UTIL.getDefaultRootDirPath(), TABLENAME);
196    assertTrue(fs.exists(tableDir));
197
198    // 4 - Check if all the target column family exist in FS
199    FileStatus[] fileStatus = fs.listStatus(tableDir);
200    foundCF = false;
201    for (int i = 0; i < fileStatus.length; i++) {
202      if (fileStatus[i].isDirectory() == true) {
203        FileStatus[] cf = fs.listStatus(fileStatus[i].getPath(), new PathFilter() {
204          @Override
205          public boolean accept(Path p) {
206            if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) {
207              return false;
208            }
209            return true;
210          }
211        });
212        for (int j = 0; j < cf.length; j++) {
213          if (cf[j].isDirectory() == true && cf[j].getPath().getName().equals(cfToDelete)) {
214            foundCF = true;
215            break;
216          }
217        }
218      }
219      if (foundCF) {
220        break;
221      }
222    }
223    assertTrue(foundCF);
224
225    // TEST - Disable and delete the column family
226    if (admin.isTableEnabled(TABLENAME)) {
227      admin.disableTable(TABLENAME);
228    }
229    admin.deleteColumnFamily(TABLENAME, Bytes.toBytes(cfToDelete));
230
231    // 5 - Check if the target column family is gone from the FS
232    fileStatus = fs.listStatus(tableDir);
233    for (int i = 0; i < fileStatus.length; i++) {
234      if (fileStatus[i].isDirectory() == true) {
235        FileStatus[] cf = fs.listStatus(fileStatus[i].getPath(), new PathFilter() {
236          @Override
237          public boolean accept(Path p) {
238            if (WALSplitUtil.isSequenceIdFile(p)) {
239              return false;
240            }
241            return true;
242          }
243        });
244        for (int j = 0; j < cf.length; j++) {
245          if (cf[j].isDirectory() == true) {
246            assertFalse(cf[j].getPath().getName().equals(cfToDelete));
247          }
248        }
249      }
250    }
251
252    try {
253      // Test: delete again
254      admin.deleteColumnFamily(TABLENAME, Bytes.toBytes(cfToDelete));
255      fail("Delete a non-exist column family should fail");
256    } catch (InvalidFamilyOperationException e) {
257      // Expected.
258    }
259  }
260}