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.util; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertNotEquals; 023import static org.junit.Assert.assertNotNull; 024import static org.junit.Assert.assertNull; 025import static org.junit.Assert.assertTrue; 026import static org.junit.Assert.fail; 027 028import java.io.IOException; 029import java.util.Arrays; 030import java.util.Comparator; 031import java.util.Map; 032import org.apache.hadoop.fs.FSDataOutputStream; 033import org.apache.hadoop.fs.FileStatus; 034import org.apache.hadoop.fs.FileSystem; 035import org.apache.hadoop.fs.Path; 036import org.apache.hadoop.hbase.HBaseClassTestRule; 037import org.apache.hadoop.hbase.HBaseCommonTestingUtility; 038import org.apache.hadoop.hbase.HConstants; 039import org.apache.hadoop.hbase.TableDescriptors; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 042import org.apache.hadoop.hbase.client.TableDescriptor; 043import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.MiscTests; 046import org.junit.AfterClass; 047import org.junit.Before; 048import org.junit.ClassRule; 049import org.junit.Rule; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052import org.junit.rules.TestName; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056/** 057 * Tests for {@link FSTableDescriptors}. 058 */ 059// Do not support to be executed in he same JVM as other tests 060@Category({ MiscTests.class, MediumTests.class }) 061public class TestFSTableDescriptors { 062 063 @ClassRule 064 public static final HBaseClassTestRule CLASS_RULE = 065 HBaseClassTestRule.forClass(TestFSTableDescriptors.class); 066 067 private static final HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility(); 068 private static final Logger LOG = LoggerFactory.getLogger(TestFSTableDescriptors.class); 069 070 @Rule 071 public TestName name = new TestName(); 072 073 private Path testDir; 074 075 @Before 076 public void setUp() { 077 testDir = UTIL.getDataTestDir(name.getMethodName()); 078 } 079 080 @AfterClass 081 public static void tearDownAfterClass() { 082 UTIL.cleanupTestDir(); 083 } 084 085 @Test(expected = IllegalArgumentException.class) 086 public void testRegexAgainstOldStyleTableInfo() { 087 Path p = new Path(testDir, FSTableDescriptors.TABLEINFO_FILE_PREFIX); 088 int i = FSTableDescriptors.getTableInfoSequenceIdAndFileLength(p).sequenceId; 089 assertEquals(0, i); 090 // Assert it won't eat garbage -- that it fails 091 p = new Path(testDir, "abc"); 092 FSTableDescriptors.getTableInfoSequenceIdAndFileLength(p); 093 } 094 095 @Test 096 public void testCreateAndUpdate() throws IOException { 097 TableDescriptor htd = 098 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); 099 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 100 FSTableDescriptors fstd = new FSTableDescriptors(fs, testDir); 101 assertTrue(fstd.createTableDescriptor(htd)); 102 assertFalse(fstd.createTableDescriptor(htd)); 103 Path tableInfoDir = new Path(CommonFSUtils.getTableDir(testDir, htd.getTableName()), 104 FSTableDescriptors.TABLEINFO_DIR); 105 FileStatus[] statuses = fs.listStatus(tableInfoDir); 106 assertEquals("statuses.length=" + statuses.length, 1, statuses.length); 107 for (int i = 0; i < 10; i++) { 108 fstd.update(htd); 109 } 110 statuses = fs.listStatus(tableInfoDir); 111 assertEquals(1, statuses.length); 112 } 113 114 @Test 115 public void testSequenceIdAdvancesOnTableInfo() throws IOException { 116 TableDescriptor htd = 117 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); 118 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 119 FSTableDescriptors fstd = new FSTableDescriptors(fs, testDir); 120 Path previousPath = null; 121 int previousSeqId = -1; 122 for (int i = 0; i < 10; i++) { 123 Path path = fstd.updateTableDescriptor(htd); 124 int seqId = FSTableDescriptors.getTableInfoSequenceIdAndFileLength(path).sequenceId; 125 if (previousPath != null) { 126 // Assert we cleaned up the old file. 127 assertTrue(!fs.exists(previousPath)); 128 assertEquals(previousSeqId + 1, seqId); 129 } 130 previousPath = path; 131 previousSeqId = seqId; 132 } 133 } 134 135 @Test 136 public void testFormatTableInfoSequenceId() { 137 Path p0 = assertWriteAndReadSequenceId(0); 138 // Assert p0 has format we expect. 139 StringBuilder sb = new StringBuilder(); 140 for (int i = 0; i < FSTableDescriptors.WIDTH_OF_SEQUENCE_ID; i++) { 141 sb.append("0"); 142 } 143 assertEquals(FSTableDescriptors.TABLEINFO_FILE_PREFIX + "." + sb.toString() + ".0", 144 p0.getName()); 145 // Check a few more. 146 Path p2 = assertWriteAndReadSequenceId(2); 147 Path p10000 = assertWriteAndReadSequenceId(10000); 148 // Get a .tablinfo that has no sequenceid suffix. 149 Path p = new Path(p0.getParent(), FSTableDescriptors.TABLEINFO_FILE_PREFIX); 150 FileStatus fs = new FileStatus(0, false, 0, 0, 0, p); 151 FileStatus fs0 = new FileStatus(0, false, 0, 0, 0, p0); 152 FileStatus fs2 = new FileStatus(0, false, 0, 0, 0, p2); 153 FileStatus fs10000 = new FileStatus(0, false, 0, 0, 0, p10000); 154 Comparator<FileStatus> comparator = FSTableDescriptors.TABLEINFO_FILESTATUS_COMPARATOR; 155 assertTrue(comparator.compare(fs, fs0) > 0); 156 assertTrue(comparator.compare(fs0, fs2) > 0); 157 assertTrue(comparator.compare(fs2, fs10000) > 0); 158 } 159 160 private Path assertWriteAndReadSequenceId(final int i) { 161 Path p = 162 new Path(testDir, FSTableDescriptors.getTableInfoFileName(i, HConstants.EMPTY_BYTE_ARRAY)); 163 int ii = FSTableDescriptors.getTableInfoSequenceIdAndFileLength(p).sequenceId; 164 assertEquals(i, ii); 165 return p; 166 } 167 168 @Test 169 public void testRemoves() throws IOException { 170 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 171 // Cleanup old tests if any detrius laying around. 172 TableDescriptors htds = new FSTableDescriptors(fs, testDir); 173 TableDescriptor htd = 174 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); 175 htds.update(htd); 176 assertNotNull(htds.remove(htd.getTableName())); 177 assertNull(htds.remove(htd.getTableName())); 178 } 179 180 @Test 181 public void testReadingHTDFromFS() throws IOException { 182 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 183 TableDescriptor htd = 184 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); 185 FSTableDescriptors fstd = new FSTableDescriptors(fs, testDir); 186 fstd.createTableDescriptor(htd); 187 TableDescriptor td2 = 188 FSTableDescriptors.getTableDescriptorFromFs(fs, testDir, htd.getTableName()); 189 assertTrue(htd.equals(td2)); 190 } 191 192 @Test 193 public void testTableDescriptors() throws IOException, InterruptedException { 194 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 195 // Cleanup old tests if any debris laying around. 196 FSTableDescriptors htds = new FSTableDescriptors(fs, testDir) { 197 @Override 198 public TableDescriptor get(TableName tablename) { 199 LOG.info(tablename + ", cachehits=" + this.cachehits); 200 return super.get(tablename); 201 } 202 }; 203 final int count = 10; 204 // Write out table infos. 205 for (int i = 0; i < count; i++) { 206 htds.createTableDescriptor( 207 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName() + i)).build()); 208 } 209 210 for (int i = 0; i < count; i++) { 211 assertTrue(htds.get(TableName.valueOf(name.getMethodName() + i)) != null); 212 } 213 for (int i = 0; i < count; i++) { 214 assertTrue(htds.get(TableName.valueOf(name.getMethodName() + i)) != null); 215 } 216 // Update the table infos 217 for (int i = 0; i < count; i++) { 218 TableDescriptorBuilder builder = 219 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName() + i)); 220 builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of("" + i)); 221 htds.update(builder.build()); 222 } 223 // Wait a while so mod time we write is for sure different. 224 Thread.sleep(100); 225 for (int i = 0; i < count; i++) { 226 assertTrue(htds.get(TableName.valueOf(name.getMethodName() + i)) != null); 227 } 228 for (int i = 0; i < count; i++) { 229 assertTrue(htds.get(TableName.valueOf(name.getMethodName() + i)) != null); 230 } 231 assertEquals(count * 4, htds.invocations); 232 assertTrue("expected=" + (count * 2) + ", actual=" + htds.cachehits, 233 htds.cachehits >= (count * 2)); 234 } 235 236 @Test 237 public void testTableDescriptorsNoCache() throws IOException, InterruptedException { 238 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 239 // Cleanup old tests if any debris laying around. 240 FSTableDescriptors htds = new FSTableDescriptorsTest(fs, testDir, false); 241 final int count = 10; 242 // Write out table infos. 243 for (int i = 0; i < count; i++) { 244 htds.createTableDescriptor( 245 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName() + i)).build()); 246 } 247 248 for (int i = 0; i < 2 * count; i++) { 249 assertNotNull("Expected HTD, got null instead", 250 htds.get(TableName.valueOf(name.getMethodName() + i % 2))); 251 } 252 // Update the table infos 253 for (int i = 0; i < count; i++) { 254 TableDescriptorBuilder builder = 255 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName() + i)); 256 builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of("" + i)); 257 htds.update(builder.build()); 258 } 259 for (int i = 0; i < count; i++) { 260 assertNotNull("Expected HTD, got null instead", 261 htds.get(TableName.valueOf(name.getMethodName() + i))); 262 assertTrue("Column Family " + i + " missing", htds 263 .get(TableName.valueOf(name.getMethodName() + i)).hasColumnFamily(Bytes.toBytes("" + i))); 264 } 265 assertEquals(count * 4, htds.invocations); 266 assertEquals("expected=0, actual=" + htds.cachehits, 0, htds.cachehits); 267 } 268 269 @Test 270 public void testGetAll() throws IOException, InterruptedException { 271 final String name = "testGetAll"; 272 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 273 // Cleanup old tests if any debris laying around. 274 FSTableDescriptors htds = new FSTableDescriptorsTest(fs, testDir); 275 final int count = 4; 276 // Write out table infos. 277 for (int i = 0; i < count; i++) { 278 htds.createTableDescriptor( 279 TableDescriptorBuilder.newBuilder(TableName.valueOf(name + i)).build()); 280 } 281 // add hbase:meta 282 htds 283 .createTableDescriptor(TableDescriptorBuilder.newBuilder(TableName.META_TABLE_NAME).build()); 284 assertEquals("getAll() didn't return all TableDescriptors, expected: " + (count + 1) + " got: " 285 + htds.getAll().size(), count + 1, htds.getAll().size()); 286 } 287 288 @Test 289 public void testGetAllOrdering() throws Exception { 290 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 291 FSTableDescriptors tds = new FSTableDescriptorsTest(fs, testDir); 292 293 String[] tableNames = new String[] { "foo", "bar", "foo:bar", "bar:foo" }; 294 for (String tableName : tableNames) { 295 tds.createTableDescriptor( 296 TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName)).build()); 297 } 298 299 Map<String, TableDescriptor> tables = tds.getAll(); 300 // Remove hbase:meta from list. It shows up now since we made it dynamic. The schema 301 // is written into the fs by the FSTableDescriptors constructor now where before it 302 // didn't. 303 tables.remove(TableName.META_TABLE_NAME.getNameAsString()); 304 assertEquals(4, tables.size()); 305 306 String[] tableNamesOrdered = 307 new String[] { "bar:foo", "default:bar", "default:foo", "foo:bar" }; 308 int i = 0; 309 for (Map.Entry<String, TableDescriptor> entry : tables.entrySet()) { 310 assertEquals(tableNamesOrdered[i], entry.getKey()); 311 assertEquals(tableNamesOrdered[i], 312 entry.getValue().getTableName().getNameWithNamespaceInclAsString()); 313 i++; 314 } 315 } 316 317 @Test 318 public void testCacheConsistency() throws IOException, InterruptedException { 319 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 320 // Cleanup old tests if any debris laying around. 321 FSTableDescriptors chtds = new FSTableDescriptorsTest(fs, testDir); 322 FSTableDescriptors nonchtds = new FSTableDescriptorsTest(fs, testDir, false); 323 324 final int count = 10; 325 // Write out table infos via non-cached FSTableDescriptors 326 for (int i = 0; i < count; i++) { 327 nonchtds.createTableDescriptor( 328 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName() + i)).build()); 329 } 330 331 // Calls to getAll() won't increase the cache counter, do per table. 332 for (int i = 0; i < count; i++) { 333 assertTrue(chtds.get(TableName.valueOf(name.getMethodName() + i)) != null); 334 } 335 336 assertTrue(nonchtds.getAll().size() == chtds.getAll().size()); 337 338 // add a new entry for random table name. 339 TableName random = TableName.valueOf("random"); 340 TableDescriptor htd = TableDescriptorBuilder.newBuilder(random).build(); 341 nonchtds.createTableDescriptor(htd); 342 343 // random will only increase the cachehit by 1 344 assertEquals(nonchtds.getAll().size(), chtds.getAll().size() + 1); 345 346 for (Map.Entry<String, TableDescriptor> entry : chtds.getAll().entrySet()) { 347 String t = (String) entry.getKey(); 348 TableDescriptor nchtd = entry.getValue(); 349 assertTrue( 350 "expected " + htd.toString() + " got: " + chtds.get(TableName.valueOf(t)).toString(), 351 (nchtd.equals(chtds.get(TableName.valueOf(t))))); 352 } 353 // this is by design, for FSTableDescriptor with cache enabled, once we have done a full scan 354 // and load all the table descriptors to cache, we will not go to file system again, as the only 355 // way to update table descriptor is to through us so we can cache it when updating. 356 assertNotNull(nonchtds.get(random)); 357 assertNull(chtds.get(random)); 358 } 359 360 @Test 361 public void testNoSuchTable() throws IOException { 362 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 363 // Cleanup old tests if any detrius laying around. 364 TableDescriptors htds = new FSTableDescriptors(fs, testDir); 365 assertNull("There shouldn't be any HTD for this table", 366 htds.get(TableName.valueOf("NoSuchTable"))); 367 } 368 369 @Test 370 public void testUpdates() throws IOException { 371 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 372 // Cleanup old tests if any detrius laying around. 373 TableDescriptors htds = new FSTableDescriptors(fs, testDir); 374 TableDescriptor htd = 375 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); 376 htds.update(htd); 377 htds.update(htd); 378 htds.update(htd); 379 } 380 381 @Test 382 public void testTableInfoFileStatusComparator() { 383 FileStatus bare = new FileStatus(0, false, 0, 0, -1, 384 new Path("/tmp", FSTableDescriptors.TABLEINFO_FILE_PREFIX)); 385 FileStatus future = new FileStatus(0, false, 0, 0, -1, 386 new Path("/tmp/tablinfo." + EnvironmentEdgeManager.currentTime())); 387 FileStatus farFuture = new FileStatus(0, false, 0, 0, -1, 388 new Path("/tmp/tablinfo." + EnvironmentEdgeManager.currentTime() + 1000)); 389 FileStatus[] alist = { bare, future, farFuture }; 390 FileStatus[] blist = { bare, farFuture, future }; 391 FileStatus[] clist = { farFuture, bare, future }; 392 Comparator<FileStatus> c = FSTableDescriptors.TABLEINFO_FILESTATUS_COMPARATOR; 393 Arrays.sort(alist, c); 394 Arrays.sort(blist, c); 395 Arrays.sort(clist, c); 396 // Now assert all sorted same in way we want. 397 for (int i = 0; i < alist.length; i++) { 398 assertTrue(alist[i].equals(blist[i])); 399 assertTrue(blist[i].equals(clist[i])); 400 assertTrue(clist[i].equals(i == 0 ? farFuture : i == 1 ? future : bare)); 401 } 402 } 403 404 @Test 405 public void testReadingInvalidDirectoryFromFS() throws IOException { 406 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 407 try { 408 new FSTableDescriptors(fs, CommonFSUtils.getRootDir(UTIL.getConfiguration())) 409 .get(TableName.valueOf(HConstants.HBASE_TEMP_DIRECTORY)); 410 fail("Shouldn't be able to read a table descriptor for the archive directory."); 411 } catch (Exception e) { 412 LOG.debug("Correctly got error when reading a table descriptor from the archive directory: " 413 + e.getMessage()); 414 } 415 } 416 417 @Test 418 public void testCreateTableDescriptorUpdatesIfExistsAlready() throws IOException { 419 TableDescriptor htd = 420 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); 421 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 422 FSTableDescriptors fstd = new FSTableDescriptors(fs, testDir); 423 assertTrue(fstd.createTableDescriptor(htd)); 424 assertFalse(fstd.createTableDescriptor(htd)); 425 htd = TableDescriptorBuilder.newBuilder(htd) 426 .setValue(Bytes.toBytes("mykey"), Bytes.toBytes("myValue")).build(); 427 assertTrue(fstd.createTableDescriptor(htd)); // this will re-create 428 Path tableDir = CommonFSUtils.getTableDir(testDir, htd.getTableName()); 429 assertEquals(htd, FSTableDescriptors.getTableDescriptorFromFs(fs, tableDir)); 430 } 431 432 @Test 433 public void testIgnoreBrokenTableDescriptorFiles() throws IOException { 434 TableDescriptor htd = TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())) 435 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("cf")).build(); 436 TableDescriptor newHtd = 437 TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())) 438 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("cf2")).build(); 439 assertNotEquals(newHtd, htd); 440 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 441 FSTableDescriptors fstd = new FSTableDescriptors(fs, testDir, false, false); 442 fstd.update(htd); 443 byte[] bytes = TableDescriptorBuilder.toByteArray(newHtd); 444 Path tableDir = CommonFSUtils.getTableDir(testDir, htd.getTableName()); 445 Path tableInfoDir = new Path(tableDir, FSTableDescriptors.TABLEINFO_DIR); 446 FileStatus[] statuses = fs.listStatus(tableInfoDir); 447 assertEquals(1, statuses.length); 448 int seqId = 449 FSTableDescriptors.getTableInfoSequenceIdAndFileLength(statuses[0].getPath()).sequenceId + 1; 450 Path brokenFile = new Path(tableInfoDir, FSTableDescriptors.getTableInfoFileName(seqId, bytes)); 451 try (FSDataOutputStream out = fs.create(brokenFile)) { 452 out.write(bytes, 0, bytes.length / 2); 453 } 454 assertTrue(fs.exists(brokenFile)); 455 TableDescriptor getTd = fstd.get(htd.getTableName()); 456 assertEquals(htd, getTd); 457 assertFalse(fs.exists(brokenFile)); 458 } 459 460 private static class FSTableDescriptorsTest extends FSTableDescriptors { 461 462 public FSTableDescriptorsTest(FileSystem fs, Path rootdir) { 463 this(fs, rootdir, true); 464 } 465 466 public FSTableDescriptorsTest(FileSystem fs, Path rootdir, boolean usecache) { 467 super(fs, rootdir, false, usecache); 468 } 469 470 @Override 471 public TableDescriptor get(TableName tablename) { 472 LOG.info((super.isUsecache() ? "Cached" : "Non-Cached") + " TableDescriptor.get() on " 473 + tablename + ", cachehits=" + this.cachehits); 474 return super.get(tablename); 475 } 476 } 477}