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}