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.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.AfterClass;
042import org.junit.Assert;
043import org.junit.Before;
044import org.junit.BeforeClass;
045import org.junit.ClassRule;
046import org.junit.Rule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.junit.rules.TestName;
050
051/**
052 * Verify that the HTableDescriptor is updated after addColumn(), deleteColumn() and modifyTable()
053 * operations.
054 */
055@Category({ MasterTests.class, MediumTests.class })
056public class TestTableDescriptorModificationFromClient {
057
058  @ClassRule
059  public static final HBaseClassTestRule CLASS_RULE =
060    HBaseClassTestRule.forClass(TestTableDescriptorModificationFromClient.class);
061
062  @Rule
063  public TestName name = new TestName();
064  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
065  private static TableName TABLE_NAME = null;
066  private static final byte[] FAMILY_0 = Bytes.toBytes("cf0");
067  private static final byte[] FAMILY_1 = Bytes.toBytes("cf1");
068
069  /**
070   * Start up a mini cluster and put a small table of empty regions into it.
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    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
093      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
094    admin.createTable(tableDescriptor);
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      TableDescriptor modifiedtableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
102        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
103        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1)).build();
104      admin.modifyTable(modifiedtableDescriptor);
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    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
116      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
117    admin.createTable(tableDescriptor);
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, ColumnFamilyDescriptorBuilder.of(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    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
136      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
137    admin.createTable(tableDescriptor);
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, ColumnFamilyDescriptorBuilder.of(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, ColumnFamilyDescriptorBuilder.of(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    ColumnFamilyDescriptor cfDescriptor = ColumnFamilyDescriptorBuilder.of(FAMILY_0);
165    int blockSize = cfDescriptor.getBlocksize();
166    // Create a table with one families
167    TableDescriptor tableDescriptor =
168      TableDescriptorBuilder.newBuilder(TABLE_NAME).setColumnFamily(cfDescriptor).build();
169    admin.createTable(tableDescriptor);
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 =
177        ColumnFamilyDescriptorBuilder.newBuilder(cfDescriptor).setBlocksize(newBlockSize).build();
178
179      // Modify colymn family
180      admin.modifyColumnFamily(TABLE_NAME, cfDescriptor);
181
182      TableDescriptor htd = admin.getDescriptor(TABLE_NAME);
183      ColumnFamilyDescriptor hcfd = htd.getColumnFamily(FAMILY_0);
184      assertTrue(hcfd.getBlocksize() == newBlockSize);
185    } finally {
186      admin.deleteTable(TABLE_NAME);
187    }
188  }
189
190  @Test
191  public void testModifyNonExistingColumnFamily() throws IOException {
192    Admin admin = TEST_UTIL.getAdmin();
193
194    ColumnFamilyDescriptor cfDescriptor = ColumnFamilyDescriptorBuilder.of(FAMILY_1);
195    int blockSize = cfDescriptor.getBlocksize();
196    // Create a table with one families
197    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
198      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0)).build();
199    admin.createTable(tableDescriptor);
200    admin.disableTable(TABLE_NAME);
201    try {
202      // Verify the table descriptor
203      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
204
205      int newBlockSize = 2 * blockSize;
206      cfDescriptor =
207        ColumnFamilyDescriptorBuilder.newBuilder(cfDescriptor).setBlocksize(newBlockSize).build();
208
209      // Modify a column family that is not in the table.
210      try {
211        admin.modifyColumnFamily(TABLE_NAME, cfDescriptor);
212        Assert.fail("Modify a non-exist column family should fail");
213      } catch (InvalidFamilyOperationException e) {
214        // Expected.
215      }
216
217    } finally {
218      admin.deleteTable(TABLE_NAME);
219    }
220  }
221
222  @Test
223  public void testDeleteColumn() throws IOException {
224    Admin admin = TEST_UTIL.getAdmin();
225    // Create a table with two families
226    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
227      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
228      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1)).build();
229    admin.createTable(tableDescriptor);
230    admin.disableTable(TABLE_NAME);
231    try {
232      // Verify the table descriptor
233      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
234
235      // Modify the table removing one family and verify the descriptor
236      admin.deleteColumnFamily(TABLE_NAME, FAMILY_1);
237      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
238    } finally {
239      admin.deleteTable(TABLE_NAME);
240    }
241  }
242
243  @Test
244  public void testDeleteSameColumnFamilyTwice() throws IOException {
245    Admin admin = TEST_UTIL.getAdmin();
246    // Create a table with two families
247    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TABLE_NAME)
248      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
249      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1)).build();
250    admin.createTable(tableDescriptor);
251    admin.disableTable(TABLE_NAME);
252    try {
253      // Verify the table descriptor
254      verifyTableDescriptor(TABLE_NAME, FAMILY_0, FAMILY_1);
255
256      // Modify the table removing one family and verify the descriptor
257      admin.deleteColumnFamily(TABLE_NAME, FAMILY_1);
258      verifyTableDescriptor(TABLE_NAME, FAMILY_0);
259
260      try {
261        // Delete again - expect failure
262        admin.deleteColumnFamily(TABLE_NAME, FAMILY_1);
263        Assert.fail("Delete a non-exist column family should fail");
264      } catch (Exception e) {
265        // Expected.
266      }
267    } finally {
268      admin.deleteTable(TABLE_NAME);
269    }
270  }
271
272  private void verifyTableDescriptor(final TableName tableName, final byte[]... families)
273    throws IOException {
274    Admin admin = TEST_UTIL.getAdmin();
275
276    // Verify descriptor from master
277    TableDescriptor htd = admin.getDescriptor(tableName);
278    verifyTableDescriptor(htd, tableName, families);
279
280    // Verify descriptor from HDFS
281    MasterFileSystem mfs = TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterFileSystem();
282    Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), tableName);
283    TableDescriptor td = FSTableDescriptors.getTableDescriptorFromFs(mfs.getFileSystem(), tableDir);
284    verifyTableDescriptor(td, tableName, families);
285  }
286
287  private void verifyTableDescriptor(final TableDescriptor htd, final TableName tableName,
288    final byte[]... families) {
289    Set<byte[]> htdFamilies = htd.getColumnFamilyNames();
290    assertEquals(tableName, htd.getTableName());
291    assertEquals(families.length, htdFamilies.size());
292    for (byte[] familyName : families) {
293      assertTrue("Expected family " + Bytes.toString(familyName), htdFamilies.contains(familyName));
294    }
295  }
296}