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