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.assertTrue;
022import static org.junit.jupiter.api.Assertions.fail;
023
024import java.io.IOException;
025import java.util.Set;
026import org.apache.hadoop.fs.Path;
027import org.apache.hadoop.hbase.HBaseTestingUtil;
028import org.apache.hadoop.hbase.InvalidFamilyOperationException;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.Admin;
031import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
033import org.apache.hadoop.hbase.client.TableDescriptor;
034import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
035import org.apache.hadoop.hbase.master.MasterFileSystem;
036import org.apache.hadoop.hbase.testclassification.MasterTests;
037import org.apache.hadoop.hbase.testclassification.MediumTests;
038import org.apache.hadoop.hbase.util.Bytes;
039import org.apache.hadoop.hbase.util.CommonFSUtils;
040import org.apache.hadoop.hbase.util.FSTableDescriptors;
041import org.junit.jupiter.api.AfterAll;
042import org.junit.jupiter.api.BeforeAll;
043import org.junit.jupiter.api.BeforeEach;
044import org.junit.jupiter.api.Tag;
045import org.junit.jupiter.api.Test;
046import org.junit.jupiter.api.TestInfo;
047
048/**
049 * Verify that the HTableDescriptor is updated after addColumn(), deleteColumn() and modifyTable()
050 * operations.
051 */
052@Tag(MasterTests.TAG)
053@Tag(MediumTests.TAG)
054public class TestTableDescriptorModificationFromClient {
055  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
056  private static TableName TABLE_NAME = null;
057  private static final byte[] FAMILY_0 = Bytes.toBytes("cf0");
058  private static final byte[] FAMILY_1 = Bytes.toBytes("cf1");
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(1);
066  }
067
068  @BeforeEach
069  public void setup(TestInfo testInfo) {
070    TABLE_NAME = TableName.valueOf(testInfo.getTestMethod().get().getName());
071  }
072
073  @AfterAll
074  public static void afterAllTests() throws Exception {
075    TEST_UTIL.shutdownMiniCluster();
076  }
077
078  @Test
079  public void testModifyTable() throws IOException {
080    Admin admin = TEST_UTIL.getAdmin();
081    // Create a table with one family
082    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
083      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
084    admin.createTable(tableDescriptor);
085    admin.disableTable(TABLE_NAME);
086    try {
087      // Verify the table descriptor
088      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
089
090      // Modify the table adding another family and verify the descriptor
091      TableDescriptor modifiedtableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
092        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
093        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1)).build();
094      admin.modifyTable(modifiedtableDescriptor);
095      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
096    } finally {
097      admin.deleteTable(TABLE_NAME);
098    }
099  }
100
101  @Test
102  public void testAddColumn() throws IOException {
103    Admin admin = TEST_UTIL.getAdmin();
104    // Create a table with two families
105    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
106      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
107    admin.createTable(tableDescriptor);
108    admin.disableTable(TABLE_NAME);
109    try {
110      // Verify the table descriptor
111      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
112
113      // Modify the table removing one family and verify the descriptor
114      admin.addColumnFamily(TABLE_NAME, ColumnFamilyDescriptorBuilder.of(FAMILY_1));
115      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
116    } finally {
117      admin.deleteTable(TABLE_NAME);
118    }
119  }
120
121  @Test
122  public void testAddSameColumnFamilyTwice() throws IOException {
123    Admin admin = TEST_UTIL.getAdmin();
124    // Create a table with one families
125    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
126      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
127    admin.createTable(tableDescriptor);
128    admin.disableTable(TABLE_NAME);
129    try {
130      // Verify the table descriptor
131      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
132
133      // Modify the table removing one family and verify the descriptor
134      admin.addColumnFamily(TABLE_NAME, ColumnFamilyDescriptorBuilder.of(FAMILY_1));
135      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
136
137      try {
138        // Add same column family again - expect failure
139        admin.addColumnFamily(TABLE_NAME, ColumnFamilyDescriptorBuilder.of(FAMILY_1));
140        fail("Delete a non-exist column family should fail");
141      } catch (InvalidFamilyOperationException e) {
142        // Expected.
143      }
144
145    } finally {
146      admin.deleteTable(TABLE_NAME);
147    }
148  }
149
150  @Test
151  public void testModifyColumnFamily() throws IOException {
152    Admin admin = TEST_UTIL.getAdmin();
153
154    ColumnFamilyDescriptor cfDescriptor = ColumnFamilyDescriptorBuilder.of(FAMILY_0);
155    int blockSize = cfDescriptor.getBlocksize();
156    // Create a table with one families
157    TableDescriptor tableDescriptor =
158      TableDescriptorBuilder.newBuilder(TABLE_NAME).setColumnFamily(cfDescriptor).build();
159    admin.createTable(tableDescriptor);
160    admin.disableTable(TABLE_NAME);
161    try {
162      // Verify the table descriptor
163      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
164
165      int newBlockSize = 2 * blockSize;
166      cfDescriptor =
167        ColumnFamilyDescriptorBuilder.newBuilder(cfDescriptor).setBlocksize(newBlockSize).build();
168
169      // Modify colymn family
170      admin.modifyColumnFamily(TABLE_NAME, cfDescriptor);
171
172      TableDescriptor htd = admin.getDescriptor(TABLE_NAME);
173      ColumnFamilyDescriptor hcfd = htd.getColumnFamily(FAMILY_0);
174      assertTrue(hcfd.getBlocksize() == newBlockSize);
175    } finally {
176      admin.deleteTable(TABLE_NAME);
177    }
178  }
179
180  @Test
181  public void testModifyNonExistingColumnFamily() throws IOException {
182    Admin admin = TEST_UTIL.getAdmin();
183
184    ColumnFamilyDescriptor cfDescriptor = ColumnFamilyDescriptorBuilder.of(FAMILY_1);
185    int blockSize = cfDescriptor.getBlocksize();
186    // Create a table with one families
187    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
188      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
189    admin.createTable(tableDescriptor);
190    admin.disableTable(TABLE_NAME);
191    try {
192      // Verify the table descriptor
193      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
194
195      int newBlockSize = 2 * blockSize;
196      cfDescriptor =
197        ColumnFamilyDescriptorBuilder.newBuilder(cfDescriptor).setBlocksize(newBlockSize).build();
198
199      // Modify a column family that is not in the table.
200      try {
201        admin.modifyColumnFamily(TABLE_NAME, cfDescriptor);
202        fail("Modify a non-exist column family should fail");
203      } catch (InvalidFamilyOperationException e) {
204        // Expected.
205      }
206
207    } finally {
208      admin.deleteTable(TABLE_NAME);
209    }
210  }
211
212  @Test
213  public void testDeleteColumn() throws IOException {
214    Admin admin = TEST_UTIL.getAdmin();
215    // Create a table with two families
216    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
217      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
218      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1)).build();
219    admin.createTable(tableDescriptor);
220    admin.disableTable(TABLE_NAME);
221    try {
222      // Verify the table descriptor
223      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
224
225      // Modify the table removing one family and verify the descriptor
226      admin.deleteColumnFamily(TABLE_NAME, FAMILY_1);
227      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
228    } finally {
229      admin.deleteTable(TABLE_NAME);
230    }
231  }
232
233  @Test
234  public void testDeleteSameColumnFamilyTwice() throws IOException {
235    Admin admin = TEST_UTIL.getAdmin();
236    // Create a table with two families
237    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
238      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
239      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1)).build();
240    admin.createTable(tableDescriptor);
241    admin.disableTable(TABLE_NAME);
242    try {
243      // Verify the table descriptor
244      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
245
246      // Modify the table removing one family and verify the descriptor
247      admin.deleteColumnFamily(TABLE_NAME, FAMILY_1);
248      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
249
250      try {
251        // Delete again - expect failure
252        admin.deleteColumnFamily(TABLE_NAME, FAMILY_1);
253        fail("Delete a non-exist column family should fail");
254      } catch (Exception e) {
255        // Expected.
256      }
257    } finally {
258      admin.deleteTable(TABLE_NAME);
259    }
260  }
261
262  private void verifyTableDescriptor(final TableName tableName, final byte[]... families)
263    throws IOException {
264    Admin admin = TEST_UTIL.getAdmin();
265
266    // Verify descriptor from master
267    TableDescriptor htd = admin.getDescriptor(tableName);
268    verifyTableDescriptor(htd, tableName, families);
269
270    // Verify descriptor from HDFS
271    MasterFileSystem mfs = TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterFileSystem();
272    Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), tableName);
273    TableDescriptor td = FSTableDescriptors.getTableDescriptorFromFs(mfs.getFileSystem(), tableDir);
274    verifyTableDescriptor(td, tableName, families);
275  }
276
277  private void verifyTableDescriptor(final TableDescriptor htd, final TableName tableName,
278    final byte[]... families) {
279    Set<byte[]> htdFamilies = htd.getColumnFamilyNames();
280    assertEquals(tableName, htd.getTableName());
281    assertEquals(families.length, htdFamilies.size());
282    for (byte[] familyName : families) {
283      assertTrue(htdFamilies.contains(familyName), "Expected family " + Bytes.toString(familyName));
284    }
285  }
286}