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.regionserver; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.List; 027import java.util.Optional; 028import java.util.concurrent.ConcurrentLinkedQueue; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.ArrayBackedTag; 031import org.apache.hadoop.hbase.Cell; 032import org.apache.hadoop.hbase.CellScanner; 033import org.apache.hadoop.hbase.CellUtil; 034import org.apache.hadoop.hbase.ExtendedCell; 035import org.apache.hadoop.hbase.ExtendedCellScanner; 036import org.apache.hadoop.hbase.HBaseTestingUtil; 037import org.apache.hadoop.hbase.HConstants; 038import org.apache.hadoop.hbase.KeyValue; 039import org.apache.hadoop.hbase.KeyValueUtil; 040import org.apache.hadoop.hbase.PrivateCellUtil; 041import org.apache.hadoop.hbase.TableName; 042import org.apache.hadoop.hbase.Tag; 043import org.apache.hadoop.hbase.client.Append; 044import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 045import org.apache.hadoop.hbase.client.Durability; 046import org.apache.hadoop.hbase.client.Increment; 047import org.apache.hadoop.hbase.client.Mutation; 048import org.apache.hadoop.hbase.client.Put; 049import org.apache.hadoop.hbase.client.Result; 050import org.apache.hadoop.hbase.client.ResultScanner; 051import org.apache.hadoop.hbase.client.Scan; 052import org.apache.hadoop.hbase.client.Table; 053import org.apache.hadoop.hbase.client.TableDescriptor; 054import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 055import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 056import org.apache.hadoop.hbase.coprocessor.ObserverContext; 057import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; 058import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; 059import org.apache.hadoop.hbase.coprocessor.RegionObserver; 060import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 061import org.apache.hadoop.hbase.testclassification.MediumTests; 062import org.apache.hadoop.hbase.testclassification.RegionServerTests; 063import org.apache.hadoop.hbase.util.Bytes; 064import org.apache.hadoop.hbase.wal.WALEdit; 065import org.junit.jupiter.api.AfterAll; 066import org.junit.jupiter.api.AfterEach; 067import org.junit.jupiter.api.BeforeAll; 068import org.junit.jupiter.api.Test; 069import org.junit.jupiter.api.TestInfo; 070import org.junit.jupiter.params.ParameterizedTest; 071import org.junit.jupiter.params.provider.EnumSource; 072 073/** 074 * Class that test tags 075 */ 076@org.junit.jupiter.api.Tag(RegionServerTests.TAG) 077@org.junit.jupiter.api.Tag(MediumTests.TAG) 078public class TestTags { 079 080 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 081 082 @BeforeAll 083 public static void setUpBeforeClass() throws Exception { 084 Configuration conf = TEST_UTIL.getConfiguration(); 085 conf.setInt("hfile.format.version", 3); 086 conf.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY, 087 TestCoprocessorForTags.class.getName()); 088 TEST_UTIL.startMiniCluster(2); 089 } 090 091 @AfterAll 092 public static void tearDownAfterClass() throws Exception { 093 TEST_UTIL.shutdownMiniCluster(); 094 } 095 096 @AfterEach 097 public void tearDown() throws InterruptedException { 098 resetCoprocessorFlags(); 099 } 100 101 private void resetCoprocessorFlags() throws InterruptedException { 102 TestCoprocessorForTags.checkTagPresence = false; 103 // to avoid data race, here we sleep for a little while before reseting tags 104 Thread.sleep(100); 105 TestCoprocessorForTags.tags.clear(); 106 } 107 108 @Test 109 public void testTags(TestInfo testInfo) throws Exception { 110 TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 111 byte[] fam = Bytes.toBytes("info"); 112 byte[] row = Bytes.toBytes("rowa"); 113 // column names 114 byte[] qual = Bytes.toBytes("qual"); 115 byte[] row1 = Bytes.toBytes("rowb"); 116 byte[] row2 = Bytes.toBytes("rowc"); 117 118 TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName) 119 .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(fam).setBlockCacheEnabled(true) 120 .setDataBlockEncoding(DataBlockEncoding.NONE).build()) 121 .build(); 122 try (Table table = TEST_UTIL.createTable(tableDescriptor, null)) { 123 byte[] value = Bytes.toBytes("value"); 124 Put put = new Put(row); 125 put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value); 126 put.setAttribute("visibility", Bytes.toBytes("myTag")); 127 table.put(put); 128 TEST_UTIL.flush(tableName); 129 130 Put put1 = new Put(row1); 131 byte[] value1 = Bytes.toBytes("1000dfsdf"); 132 put1.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value1); 133 // put1.setAttribute("visibility", Bytes.toBytes("myTag3")); 134 table.put(put1); 135 TEST_UTIL.flush(tableName); 136 137 Put put2 = new Put(row2); 138 byte[] value2 = Bytes.toBytes("1000dfsdf"); 139 put2.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value2); 140 put2.setAttribute("visibility", Bytes.toBytes("myTag3")); 141 table.put(put2); 142 TEST_UTIL.flush(tableName); 143 144 assertResult(fam, row, qual, row2, table, value, value2, row1, value1); 145 146 TEST_UTIL.compact(tableName, false); 147 assertResult(fam, row, qual, row2, table, value, value2, row1, value1); 148 } 149 } 150 151 @Test 152 public void testFlushAndCompactionWithoutTags(TestInfo testInfo) throws Exception { 153 TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 154 byte[] fam = Bytes.toBytes("info"); 155 byte[] row = Bytes.toBytes("rowa"); 156 // column names 157 byte[] qual = Bytes.toBytes("qual"); 158 byte[] row1 = Bytes.toBytes("rowb"); 159 byte[] row2 = Bytes.toBytes("rowc"); 160 161 TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName).setColumnFamily( 162 ColumnFamilyDescriptorBuilder.newBuilder(fam).setBlockCacheEnabled(true).build()).build(); 163 try (Table table = TEST_UTIL.createTable(tableDescriptor, null)) { 164 Put put = new Put(row); 165 byte[] value = Bytes.toBytes("value"); 166 put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value); 167 table.put(put); 168 TEST_UTIL.flush(tableName); 169 170 Put put1 = new Put(row1); 171 byte[] value1 = Bytes.toBytes("1000dfsdf"); 172 put1.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value1); 173 table.put(put1); 174 TEST_UTIL.flush(tableName); 175 176 Put put2 = new Put(row2); 177 byte[] value2 = Bytes.toBytes("1000dfsdf"); 178 put2.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value2); 179 table.put(put2); 180 TEST_UTIL.flush(tableName); 181 182 Scan s = new Scan().withStartRow(row); 183 try (ResultScanner scanner = table.getScanner(s)) { 184 Result[] next = scanner.next(3); 185 for (Result result : next) { 186 ExtendedCellScanner cellScanner = result.cellScanner(); 187 cellScanner.advance(); 188 ExtendedCell current = cellScanner.current(); 189 assertEquals(0, current.getTagsLength()); 190 } 191 } 192 193 TEST_UTIL.compact(tableName, false); 194 s = new Scan().withStartRow(row); 195 try (ResultScanner scanner = table.getScanner(s)) { 196 Result[] next = scanner.next(3); 197 for (Result result : next) { 198 ExtendedCellScanner cellScanner = result.cellScanner(); 199 cellScanner.advance(); 200 ExtendedCell current = cellScanner.current(); 201 assertEquals(0, current.getTagsLength()); 202 } 203 } 204 } 205 } 206 207 @ParameterizedTest(name = "{index}: DataBlockEncoding = {0}") 208 @EnumSource(DataBlockEncoding.class) 209 public void testFlushAndCompactionwithCombinations(DataBlockEncoding encoding, TestInfo testInfo) 210 throws Exception { 211 TableName tableName = 212 TableName.valueOf(testInfo.getTestMethod().get().getName() + "-" + encoding.name()); 213 byte[] fam = Bytes.toBytes("info"); 214 byte[] row = Bytes.toBytes("rowa"); 215 // column names 216 byte[] qual = Bytes.toBytes("qual"); 217 byte[] row1 = Bytes.toBytes("rowb"); 218 byte[] row2 = Bytes.toBytes("rowc"); 219 byte[] rowd = Bytes.toBytes("rowd"); 220 byte[] rowe = Bytes.toBytes("rowe"); 221 TableDescriptor tableDescriptor = 222 TableDescriptorBuilder.newBuilder(tableName).setColumnFamily(ColumnFamilyDescriptorBuilder 223 .newBuilder(fam).setBlockCacheEnabled(true).setDataBlockEncoding(encoding).build()).build(); 224 try (Table table = TEST_UTIL.createTable(tableDescriptor, null)) { 225 Put put = new Put(row); 226 byte[] value = Bytes.toBytes("value"); 227 put.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value); 228 int bigTagLen = Short.MAX_VALUE - 5; 229 put.setAttribute("visibility", new byte[bigTagLen]); 230 table.put(put); 231 Put put1 = new Put(row1); 232 byte[] value1 = Bytes.toBytes("1000dfsdf"); 233 put1.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value1); 234 table.put(put1); 235 TEST_UTIL.flush(tableName); 236 237 put1 = new Put(row2); 238 value1 = Bytes.toBytes("1000dfsdf"); 239 put1.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value1); 240 table.put(put1); 241 TEST_UTIL.flush(tableName); 242 243 Put put2 = new Put(rowd); 244 byte[] value2 = Bytes.toBytes("1000dfsdf"); 245 put2.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value2); 246 table.put(put2); 247 put2 = new Put(rowe); 248 value2 = Bytes.toBytes("1000dfsddfdf"); 249 put2.addColumn(fam, qual, HConstants.LATEST_TIMESTAMP, value2); 250 put.setAttribute("visibility", Bytes.toBytes("ram")); 251 table.put(put2); 252 TEST_UTIL.flush(tableName); 253 254 TestCoprocessorForTags.checkTagPresence = true; 255 Scan s = new Scan().withStartRow(row); 256 s.setCaching(1); 257 try (ResultScanner scanner = table.getScanner(s)) { 258 Result next = null; 259 while ((next = scanner.next()) != null) { 260 CellScanner cellScanner = next.cellScanner(); 261 cellScanner.advance(); 262 Cell current = cellScanner.current(); 263 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 264 if (CellUtil.matchingRows(current, row)) { 265 assertEquals(1, tags.size()); 266 Tag tag = tags.get(0); 267 assertEquals(bigTagLen, tag.getValueLength()); 268 } else { 269 assertEquals(0, tags.size()); 270 } 271 } 272 } 273 resetCoprocessorFlags(); 274 275 TEST_UTIL.compact(tableName, false); 276 277 TestCoprocessorForTags.checkTagPresence = true; 278 279 try (ResultScanner scanner = table.getScanner(s)) { 280 Result next = null; 281 while ((next = scanner.next()) != null) { 282 CellScanner cellScanner = next.cellScanner(); 283 cellScanner.advance(); 284 Cell current = cellScanner.current(); 285 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 286 if (CellUtil.matchingRows(current, row)) { 287 assertEquals(1, tags.size()); 288 Tag tag = tags.get(0); 289 assertEquals(bigTagLen, tag.getValueLength()); 290 } else { 291 assertEquals(0, tags.size()); 292 } 293 } 294 } 295 } 296 } 297 298 @Test 299 public void testTagsWithAppendAndIncrement(TestInfo testInfo) throws Exception { 300 TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 301 byte[] f = Bytes.toBytes("f"); 302 byte[] q = Bytes.toBytes("q"); 303 byte[] row1 = Bytes.toBytes("r1"); 304 byte[] row2 = Bytes.toBytes("r2"); 305 306 TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName) 307 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(f)).build(); 308 309 try (Table table = TEST_UTIL.createTable(tableDescriptor, null)) { 310 Put put = new Put(row1); 311 byte[] v = Bytes.toBytes(2L); 312 put.addColumn(f, q, v); 313 put.setAttribute("visibility", Bytes.toBytes("tag1")); 314 table.put(put); 315 Increment increment = new Increment(row1); 316 increment.addColumn(f, q, 1L); 317 table.increment(increment); 318 TestCoprocessorForTags.checkTagPresence = true; 319 try (ResultScanner scanner = table.getScanner(new Scan())) { 320 Result result = scanner.next(); 321 Cell kv = result.getColumnLatestCell(f, q); 322 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 323 assertEquals(3L, 324 Bytes.toLong(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength())); 325 assertEquals(1, tags.size()); 326 assertEquals("tag1", Bytes.toString(Tag.cloneValue(tags.get(0)))); 327 } 328 resetCoprocessorFlags(); 329 330 increment = new Increment(row1); 331 increment.add(new KeyValue(row1, f, q, 1234L, v)); 332 increment.setAttribute("visibility", Bytes.toBytes("tag2")); 333 table.increment(increment); 334 TestCoprocessorForTags.checkTagPresence = true; 335 try (ResultScanner scanner = table.getScanner(new Scan())) { 336 Result result = scanner.next(); 337 Cell kv = result.getColumnLatestCell(f, q); 338 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 339 assertEquals(5L, 340 Bytes.toLong(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength())); 341 assertEquals(2, tags.size()); 342 // We cannot assume the ordering of tags 343 List<String> tagValues = new ArrayList<>(); 344 for (Tag tag : tags) { 345 tagValues.add(Bytes.toString(Tag.cloneValue(tag))); 346 } 347 assertTrue(tagValues.contains("tag1")); 348 assertTrue(tagValues.contains("tag2")); 349 } 350 resetCoprocessorFlags(); 351 352 put = new Put(row2); 353 v = Bytes.toBytes(2L); 354 put.addColumn(f, q, v); 355 table.put(put); 356 increment = new Increment(row2); 357 increment.add(new KeyValue(row2, f, q, 1234L, v)); 358 increment.setAttribute("visibility", Bytes.toBytes("tag2")); 359 table.increment(increment); 360 TestCoprocessorForTags.checkTagPresence = true; 361 try (ResultScanner scanner = table.getScanner(new Scan().withStartRow(row2))) { 362 Result result = scanner.next(); 363 Cell kv = result.getColumnLatestCell(f, q); 364 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 365 assertEquals(4L, 366 Bytes.toLong(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength())); 367 assertEquals(1, tags.size()); 368 assertEquals("tag2", Bytes.toString(Tag.cloneValue(tags.get(0)))); 369 } 370 resetCoprocessorFlags(); 371 372 // Test Append 373 byte[] row3 = Bytes.toBytes("r3"); 374 put = new Put(row3); 375 put.addColumn(f, q, Bytes.toBytes("a")); 376 put.setAttribute("visibility", Bytes.toBytes("tag1")); 377 table.put(put); 378 Append append = new Append(row3); 379 append.addColumn(f, q, Bytes.toBytes("b")); 380 table.append(append); 381 TestCoprocessorForTags.checkTagPresence = true; 382 try (ResultScanner scanner = table.getScanner(new Scan().withStartRow(row3))) { 383 assertNotNull(scanner.next()); 384 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 385 assertEquals(1, tags.size()); 386 assertEquals("tag1", Bytes.toString(Tag.cloneValue(tags.get(0)))); 387 } 388 resetCoprocessorFlags(); 389 390 append = new Append(row3); 391 append.add(new KeyValue(row3, f, q, 1234L, v)); 392 append.setAttribute("visibility", Bytes.toBytes("tag2")); 393 table.append(append); 394 TestCoprocessorForTags.checkTagPresence = true; 395 try (ResultScanner scanner = table.getScanner(new Scan().withStartRow(row3))) { 396 assertNotNull(scanner.next()); 397 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 398 assertEquals(2, tags.size()); 399 // We cannot assume the ordering of tags 400 List<String> tagValues = new ArrayList<>(); 401 for (Tag tag : tags) { 402 tagValues.add(Bytes.toString(Tag.cloneValue(tag))); 403 } 404 assertTrue(tagValues.contains("tag1")); 405 assertTrue(tagValues.contains("tag2")); 406 } 407 resetCoprocessorFlags(); 408 409 byte[] row4 = Bytes.toBytes("r4"); 410 put = new Put(row4); 411 put.addColumn(f, q, Bytes.toBytes("a")); 412 table.put(put); 413 append = new Append(row4); 414 append.add(new KeyValue(row4, f, q, 1234L, v)); 415 append.setAttribute("visibility", Bytes.toBytes("tag2")); 416 table.append(append); 417 TestCoprocessorForTags.checkTagPresence = true; 418 try (ResultScanner scanner = table.getScanner(new Scan().withStartRow(row4))) { 419 assertNotNull(scanner.next()); 420 List<Tag> tags = TestCoprocessorForTags.tags.poll(); 421 assertEquals(1, tags.size()); 422 assertEquals("tag2", Bytes.toString(Tag.cloneValue(tags.get(0)))); 423 } 424 } 425 } 426 427 private void assertResult(byte[] fam, byte[] row, byte[] qual, byte[] row2, Table table, 428 byte[] value, byte[] value2, byte[] row1, byte[] value1) throws IOException { 429 Scan s = new Scan().withStartRow(row); 430 // If filters are used this attribute can be specifically check for in 431 // filterKV method and 432 // kvs can be filtered out if the tags of interest is not found in that kv 433 s.setAttribute("visibility", Bytes.toBytes("myTag")); 434 try (ResultScanner scanner = table.getScanner(s)) { 435 Result next = scanner.next(); 436 437 assertTrue(Bytes.equals(next.getRow(), row)); 438 assertTrue(Bytes.equals(next.getValue(fam, qual), value)); 439 440 Result next2 = scanner.next(); 441 assertTrue(next2 != null); 442 assertTrue(Bytes.equals(next2.getRow(), row1)); 443 assertTrue(Bytes.equals(next2.getValue(fam, qual), value1)); 444 445 next2 = scanner.next(); 446 assertTrue(next2 != null); 447 assertTrue(Bytes.equals(next2.getRow(), row2)); 448 assertTrue(Bytes.equals(next2.getValue(fam, qual), value2)); 449 } 450 } 451 452 public static class TestCoprocessorForTags implements RegionCoprocessor, RegionObserver { 453 454 public static volatile boolean checkTagPresence = false; 455 public static final ConcurrentLinkedQueue<List<Tag>> tags = new ConcurrentLinkedQueue<>(); 456 457 @Override 458 public Optional<RegionObserver> getRegionObserver() { 459 return Optional.of(this); 460 } 461 462 @Override 463 public void prePut(final ObserverContext<? extends RegionCoprocessorEnvironment> e, 464 final Put put, final WALEdit edit, final Durability durability) throws IOException { 465 updateMutationAddingTags(put); 466 } 467 468 private void updateMutationAddingTags(final Mutation m) { 469 byte[] attribute = m.getAttribute("visibility"); 470 byte[] cf = null; 471 List<Cell> updatedCells = new ArrayList<>(); 472 if (attribute != null) { 473 for (List<? extends Cell> edits : m.getFamilyCellMap().values()) { 474 for (Cell cell : edits) { 475 KeyValue kv = KeyValueUtil.ensureKeyValue((ExtendedCell) cell); 476 if (cf == null) { 477 cf = CellUtil.cloneFamily(kv); 478 } 479 Tag tag = new ArrayBackedTag((byte) 1, attribute); 480 List<Tag> tagList = new ArrayList<>(); 481 tagList.add(tag); 482 483 KeyValue newKV = 484 new KeyValue(CellUtil.cloneRow(kv), 0, kv.getRowLength(), CellUtil.cloneFamily(kv), 0, 485 kv.getFamilyLength(), CellUtil.cloneQualifier(kv), 0, kv.getQualifierLength(), 486 kv.getTimestamp(), KeyValue.Type.codeToType(kv.getTypeByte()), 487 CellUtil.cloneValue(kv), 0, kv.getValueLength(), tagList); 488 ((List<Cell>) updatedCells).add(newKV); 489 } 490 } 491 m.getFamilyCellMap().remove(cf); 492 // Update the family map 493 m.getFamilyCellMap().put(cf, updatedCells); 494 } 495 } 496 497 @Override 498 public Result preIncrement(ObserverContext<? extends RegionCoprocessorEnvironment> e, 499 Increment increment) throws IOException { 500 updateMutationAddingTags(increment); 501 return null; 502 } 503 504 @Override 505 public Result preAppend(ObserverContext<? extends RegionCoprocessorEnvironment> e, 506 Append append) throws IOException { 507 updateMutationAddingTags(append); 508 return null; 509 } 510 511 @Override 512 public boolean postScannerNext(ObserverContext<? extends RegionCoprocessorEnvironment> e, 513 InternalScanner s, List<Result> results, int limit, boolean hasMore) throws IOException { 514 if (checkTagPresence) { 515 if (results.size() > 0) { 516 // Check tag presence in the 1st cell in 1st Result 517 Result result = results.get(0); 518 ExtendedCellScanner cellScanner = result.cellScanner(); 519 if (cellScanner.advance()) { 520 ExtendedCell cell = cellScanner.current(); 521 tags.add(PrivateCellUtil.getTags(cell)); 522 } 523 } 524 } 525 return hasMore; 526 } 527 } 528}