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}