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