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.rsgroup;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024
025import java.io.IOException;
026import java.util.List;
027import java.util.Optional;
028import java.util.regex.Pattern;
029import org.apache.hadoop.hbase.DoNotRetryIOException;
030import org.apache.hadoop.hbase.HBaseClassTestRule;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.Waiter;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
035import org.apache.hadoop.hbase.client.SnapshotDescription;
036import org.apache.hadoop.hbase.client.TableDescriptor;
037import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
038import org.apache.hadoop.hbase.constraint.ConstraintException;
039import org.apache.hadoop.hbase.testclassification.LargeTests;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.junit.After;
042import org.junit.AfterClass;
043import org.junit.Before;
044import org.junit.BeforeClass;
045import org.junit.ClassRule;
046import org.junit.Test;
047import org.junit.experimental.categories.Category;
048
049import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
050
051@Category({ LargeTests.class })
052public class TestTableDescriptorWithRSGroup extends TestRSGroupsBase {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056    HBaseClassTestRule.forClass(TestTableDescriptorWithRSGroup.class);
057  private final byte[] familyNameBytes = Bytes.toBytes("f1");
058
059  @BeforeClass
060  public static void setUp() throws Exception {
061    setUpTestBeforeClass();
062  }
063
064  @AfterClass
065  public static void tearDown() throws Exception {
066    tearDownAfterClass();
067  }
068
069  @Before
070  public void beforeMethod() throws Exception {
071    setUpBeforeMethod();
072  }
073
074  @After
075  public void afterMethod() throws Exception {
076    tearDownAfterMethod();
077  }
078
079  @Test
080  public void testCreateTableInTableDescriptorSpecificRSGroup() throws Exception {
081    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
082    assertEquals(0, newGroup.getTables().size());
083
084    // assertion is done in createTableInRSGroup
085    createTableWithRSGroupDetail(newGroup.getName());
086
087    // Create table should fail if specified rs group does not exist.
088    try {
089      TableDescriptor desc =
090        TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName.getNameAsString() + "_2"))
091          .setRegionServerGroup("nonExistingRSGroup")
092          .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
093          .build();
094      admin.createTable(desc, getSpitKeys(6));
095      fail("Should have thrown ConstraintException but no exception thrown.");
096    } catch (ConstraintException e) {
097      assertEquals(e.getMessage(), "Region server group nonExistingRSGroup does not exist.");
098    }
099  }
100
101  private void createTableWithRSGroupDetail(String newGroup) throws Exception {
102    // Create table
103
104    ColumnFamilyDescriptor f1 = ColumnFamilyDescriptorBuilder.newBuilder(familyNameBytes).build();
105    TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
106      .setRegionServerGroup(newGroup).setColumnFamily(f1).build();
107    admin.createTable(desc, getSpitKeys(5));
108
109    TEST_UTIL.waitFor(WAIT_TIMEOUT, (Waiter.Predicate<Exception>) () -> {
110      List<String> regions = getTableRegionMap().get(tableName);
111      if (regions == null) {
112        return false;
113      }
114
115      return getTableRegionMap().get(tableName).size() >= 5;
116    });
117    TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
118    Optional<String> regionServerGroup = descriptor.getRegionServerGroup();
119    assertTrue("RSGroup info is not updated into TableDescriptor when table is created.",
120      regionServerGroup.isPresent());
121    assertEquals(newGroup, regionServerGroup.get());
122  }
123
124  // moveTables should update rs group info in table descriptor
125  @Test
126  public void testMoveTablesShouldUpdateTableDescriptor() throws Exception {
127    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
128    assertEquals(0, rsGroup1.getTables().size());
129
130    createTableWithRSGroupDetail(rsGroup1.getName());
131    TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
132    Optional<String> regionServerGroup = descriptor.getRegionServerGroup();
133    assertTrue("RSGroup info is not updated into TableDescriptor when table created",
134      regionServerGroup.isPresent());
135    assertEquals(rsGroup1.getName(), regionServerGroup.get());
136
137    // moveTables
138    RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1);
139    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), rsGroup2.getName());
140    descriptor = admin.getConnection().getTable(tableName).getDescriptor();
141    regionServerGroup = descriptor.getRegionServerGroup();
142    assertTrue("RSGroup info is not updated into TableDescriptor when table moved",
143      regionServerGroup.isPresent());
144    assertEquals(rsGroup2.getName(), regionServerGroup.get());
145  }
146
147  // moveServersAndTables should update rs group info in table descriptor
148  @Test
149  public void testMoveServersAndTablesShouldUpdateTableDescriptor() throws Exception {
150    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 2);
151    assertEquals(0, rsGroup1.getTables().size());
152
153    createTableWithRSGroupDetail(rsGroup1.getName());
154    TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
155    Optional<String> regionServerGroup = descriptor.getRegionServerGroup();
156    assertTrue("RSGroup info is not updated into TableDescriptor when table created",
157      regionServerGroup.isPresent());
158    assertEquals(rsGroup1.getName(), regionServerGroup.get());
159
160    // moveServersAndTables
161    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(rsGroup1.getServers().iterator().next()),
162      Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
163
164    descriptor = admin.getConnection().getTable(tableName).getDescriptor();
165    regionServerGroup = descriptor.getRegionServerGroup();
166    assertTrue("RSGroup info is not updated into TableDescriptor when table moved",
167      regionServerGroup.isPresent());
168    assertEquals(RSGroupInfo.DEFAULT_GROUP, regionServerGroup.get());
169
170  }
171
172  @Test
173  public void testRenameRSGroupUpdatesTableDescriptor() throws Exception {
174    RSGroupInfo oldGroup = addGroup("oldGroup", 1);
175    assertEquals(0, oldGroup.getTables().size());
176
177    createTableWithRSGroupDetail(oldGroup.getName());
178
179    final String newGroupName = "newGroup";
180    rsGroupAdmin.renameRSGroup(oldGroup.getName(), newGroupName);
181    TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
182    Optional<String> regionServerGroup = descriptor.getRegionServerGroup();
183    assertTrue("RSGroup info is not updated into TableDescriptor when rs group renamed",
184      regionServerGroup.isPresent());
185    assertEquals(newGroupName, regionServerGroup.get());
186  }
187
188  @Test
189  public void testCloneSnapshotWithRSGroup() throws Exception {
190    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
191    assertEquals(0, rsGroup1.getTables().size());
192    // Creates table in rsGroup1
193    createTableWithRSGroupDetail(rsGroup1.getName());
194
195    // Create snapshot
196    final String snapshotName = "snapShot2";
197    admin.snapshot(snapshotName, tableName);
198    final List<SnapshotDescription> snapshotDescriptions =
199      admin.listSnapshots(Pattern.compile("snapShot2"));
200    assertEquals(1, snapshotDescriptions.size());
201    assertEquals(snapshotName, snapshotDescriptions.get(0).getName());
202
203    // Move table to different rs group then delete the old rs group
204    RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1);
205    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), rsGroup2.getName());
206    rsGroup2 = rsGroupAdmin.getRSGroupInfo(rsGroup2.getName());
207    assertEquals(1, rsGroup2.getTables().size());
208    assertEquals(rsGroup2.getTables().first(), tableName);
209
210    // Clone Snapshot
211    final TableName clonedTable1 = TableName.valueOf(tableName.getNameAsString() + "_1");
212    admin.cloneSnapshot(Bytes.toBytes(snapshotName), clonedTable1);
213
214    // Verify that cloned table is created into old rs group
215    final RSGroupInfo rsGroupInfoOfTable = rsGroupAdmin.getRSGroupInfoOfTable(clonedTable1);
216    assertEquals(rsGroup1.getName(), rsGroupInfoOfTable.getName());
217    TableDescriptor descriptor = admin.getConnection().getTable(clonedTable1).getDescriptor();
218    Optional<String> regionServerGroup = descriptor.getRegionServerGroup();
219    assertTrue("RSGroup info is not updated into TableDescriptor when table is cloned.",
220      regionServerGroup.isPresent());
221    assertEquals(rsGroup1.getName(), regionServerGroup.get());
222
223    // Delete table's original rs group, clone should fail.
224    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(rsGroup1.getServers()),
225      Sets.newHashSet(clonedTable1), rsGroup2.getName());
226    rsGroupAdmin.removeRSGroup(rsGroup1.getName());
227    // Clone Snapshot
228    final TableName clonedTable2 = TableName.valueOf(tableName.getNameAsString() + "_2");
229    try {
230      admin.cloneSnapshot(Bytes.toBytes(snapshotName), clonedTable2);
231      fail("Should have thrown ConstraintException but no exception thrown.");
232    } catch (ConstraintException e) {
233      assertTrue(
234        e.getCause().getMessage().contains("Region server group rsGroup1 does not exist."));
235    }
236  }
237
238  // Modify table should validate rs group existence and move rs group if required
239  @Test
240  public void testMoveTablesWhenModifyTableWithNewRSGroup() throws Exception {
241    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
242    assertEquals(0, rsGroup1.getTables().size());
243
244    createTableWithRSGroupDetail(rsGroup1.getName());
245    TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
246    Optional<String> regionServerGroup = descriptor.getRegionServerGroup();
247    assertTrue("RSGroup info is not updated into TableDescriptor when table created",
248      regionServerGroup.isPresent());
249    assertEquals(rsGroup1.getName(), regionServerGroup.get());
250
251    final TableDescriptor newTableDescriptor =
252      TableDescriptorBuilder.newBuilder(descriptor).setRegionServerGroup("rsGroup2").build();
253
254    // ConstraintException as rsGroup2 does not exits
255    try {
256      admin.modifyTable(newTableDescriptor);
257      fail("Should have thrown ConstraintException but no exception thrown.");
258    } catch (ConstraintException e) {
259      assertTrue(
260        e.getCause().getMessage().contains("Region server group rsGroup2 does not exist."));
261    }
262
263    addGroup("rsGroup2", 1);
264    // Table creation should be successful as rsGroup2 exists.
265    admin.modifyTable(newTableDescriptor);
266
267    // old group should not have table mapping now
268    rsGroup1 = rsGroupAdmin.getRSGroupInfo("rsGroup1");
269    assertEquals(0, rsGroup1.getTables().size());
270
271    RSGroupInfo rsGroup2 = rsGroupAdmin.getRSGroupInfo("rsGroup2");
272    assertEquals(1, rsGroup2.getTables().size());
273    descriptor = admin.getConnection().getTable(tableName).getDescriptor();
274    regionServerGroup = descriptor.getRegionServerGroup();
275    assertTrue("RSGroup info is not updated into TableDescriptor when table is modified.",
276      regionServerGroup.isPresent());
277    assertEquals("rsGroup2", regionServerGroup.get());
278  }
279
280  @Test
281  public void testTableShouldNotAddedIntoRSGroup_WhenModifyTableFails() throws Exception {
282    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
283    assertEquals(0, rsGroup1.getTables().size());
284
285    createTableWithRSGroupDetail(rsGroup1.getName());
286    TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
287
288    RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1);
289    final TableDescriptor newTableDescriptor = TableDescriptorBuilder.newBuilder(descriptor)
290      .setRegionServerGroup(rsGroup2.getName()).removeColumnFamily(familyNameBytes).build();
291
292    // Removed family to fail pre-check validation
293    try {
294      admin.modifyTable(newTableDescriptor);
295      fail("Should have thrown DoNotRetryIOException but no exception thrown.");
296    } catch (DoNotRetryIOException e) {
297      assertTrue(
298        e.getCause().getMessage().contains("Table should have at least one column family"));
299    }
300    rsGroup2 = rsGroupAdmin.getRSGroupInfo(rsGroup2.getName());
301    assertEquals("Table must not have moved to RSGroup as table modify failed", 0,
302      rsGroup2.getTables().size());
303  }
304
305  @Test
306  public void testTableShouldNotAddedIntoRSGroup_WhenTableCreationFails() throws Exception {
307    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
308    assertEquals(0, rsGroup1.getTables().size());
309
310    // Create TableDescriptor without a family so creation fails
311    TableDescriptor desc =
312      TableDescriptorBuilder.newBuilder(tableName).setRegionServerGroup(rsGroup1.getName()).build();
313    try {
314      admin.createTable(desc, getSpitKeys(5));
315      fail("Should have thrown DoNotRetryIOException but no exception thrown.");
316    } catch (DoNotRetryIOException e) {
317      assertTrue(
318        e.getCause().getMessage().contains("Table should have at least one column family"));
319    }
320    rsGroup1 = rsGroupAdmin.getRSGroupInfo(rsGroup1.getName());
321    assertEquals("Table must not have moved to RSGroup as table create operation failed", 0,
322      rsGroup1.getTables().size());
323  }
324
325  @Test
326  public void testSystemTablesCanBeMovedToNewRSGroupByModifyingTable() throws Exception {
327    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
328    assertEquals(0, rsGroup1.getTables().size());
329    final TableName tableName = TableName.valueOf("hbase:meta");
330    final TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor();
331    final TableDescriptor newTableDescriptor = TableDescriptorBuilder.newBuilder(descriptor)
332      .setRegionServerGroup(rsGroup1.getName()).build();
333    admin.modifyTable(newTableDescriptor);
334    final RSGroupInfo rsGroupInfoOfTable = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
335    assertEquals(rsGroup1.getName(), rsGroupInfoOfTable.getName());
336  }
337
338  @Test
339  public void testUpdateTableDescriptorOnlyIfRSGroupInfoWasStoredInTableDescriptor()
340    throws Exception {
341    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
342    assertEquals(0, rsGroup1.getTables().size());
343    // create table with rs group info stored in table descriptor
344    createTable(tableName, rsGroup1.getName());
345    final TableName table2 = TableName.valueOf(tableName.getNameAsString() + "_2");
346    // create table with no rs group info
347    createTable(table2, null);
348    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), rsGroup1.getName());
349    assertTrue("RSGroup info is not updated into TableDescriptor when table created",
350      admin.getConnection().getTable(tableName).getDescriptor().getRegionServerGroup().isPresent());
351    assertFalse(
352      "Table descriptor should not have been updated "
353        + "as rs group info was not stored in table descriptor.",
354      admin.getConnection().getTable(table2).getDescriptor().getRegionServerGroup().isPresent());
355
356    final String rsGroup2 = "rsGroup2";
357    rsGroupAdmin.renameRSGroup(rsGroup1.getName(), rsGroup2);
358    assertEquals(rsGroup2,
359      admin.getConnection().getTable(tableName).getDescriptor().getRegionServerGroup().get());
360    assertFalse(
361      "Table descriptor should not have been updated "
362        + "as rs group info was not stored in table descriptor.",
363      admin.getConnection().getTable(table2).getDescriptor().getRegionServerGroup().isPresent());
364  }
365
366  @Test
367  public void testModifyAndMoveTableScenario() throws Exception {
368    RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1);
369    assertEquals(0, rsGroup1.getTables().size());
370    // create table with rs group info stored in table descriptor
371    createTable(tableName, rsGroup1.getName());
372    final TableName table2 = TableName.valueOf(tableName.getNameAsString() + "_2");
373    // create table with no rs group info
374    createTable(table2, null);
375    rsGroupAdmin.moveTables(Sets.newHashSet(table2), rsGroup1.getName());
376
377    RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1);
378    rsGroupAdmin.moveTables(Sets.newHashSet(tableName, table2), rsGroup2.getName());
379    rsGroup2 = rsGroupAdmin.getRSGroupInfo(rsGroup2.getName());
380    assertEquals("Table movement failed.", 2, rsGroup2.getTables().size());
381  }
382
383  private void createTable(TableName tName, String rsGroupName) throws Exception {
384    ColumnFamilyDescriptor f1 = ColumnFamilyDescriptorBuilder.newBuilder(familyNameBytes).build();
385    final TableDescriptorBuilder builder =
386      TableDescriptorBuilder.newBuilder(tName).setColumnFamily(f1);
387    if (rsGroupName != null) {
388      builder.setRegionServerGroup(rsGroupName);
389    }
390    TableDescriptor desc = builder.build();
391    admin.createTable(desc, getSpitKeys(10));
392    TEST_UTIL.waitFor(WAIT_TIMEOUT, (Waiter.Predicate<Exception>) () -> {
393      List<String> regions = getTableRegionMap().get(tName);
394      if (regions == null) {
395        return false;
396      }
397
398      return getTableRegionMap().get(tName).size() >= 5;
399    });
400  }
401
402  private byte[][] getSpitKeys(int numRegions) throws IOException {
403    if (numRegions < 3) {
404      throw new IOException("Must create at least 3 regions");
405    }
406    byte[] startKey = Bytes.toBytes("aaaaa");
407    byte[] endKey = Bytes.toBytes("zzzzz");
408    return Bytes.split(startKey, endKey, numRegions - 3);
409  }
410}