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.security.visibility; 019 020import static org.junit.jupiter.api.Assertions.assertFalse; 021 022import java.util.Collections; 023import java.util.List; 024import org.apache.hadoop.hbase.ArrayBackedTag; 025import org.apache.hadoop.hbase.CellComparatorImpl; 026import org.apache.hadoop.hbase.ExtendedCell; 027import org.apache.hadoop.hbase.KeyValue; 028import org.apache.hadoop.hbase.Tag; 029import org.apache.hadoop.hbase.TagType; 030import org.apache.hadoop.hbase.regionserver.querymatcher.DeleteTracker; 031import org.apache.hadoop.hbase.testclassification.SecurityTests; 032import org.apache.hadoop.hbase.testclassification.SmallTests; 033import org.apache.hadoop.hbase.util.Bytes; 034import org.junit.jupiter.api.Test; 035 036/** 037 * Tests that {@link VisibilityScanDeleteTracker} never reports a delete marker as redundant. 038 * <p> 039 * HBASE-30036 added {@link DeleteTracker#isRedundantDelete(ExtendedCell)} so that minor compaction 040 * can drop a delete marker already covered by a previously tracked delete of equal or broader 041 * scope. The base {@link org.apache.hadoop.hbase.regionserver.querymatcher.ScanDeleteTracker} 042 * implements it purely from delete type / timestamp / qualifier, with no regard to visibility 043 * labels. 044 * <p> 045 * On cell-visibility tables a delete marker only shadows cells whose visibility expression matches 046 * (see {@link VisibilityScanDeleteTracker#isDeleted}). Two markers carrying different labels cover 047 * disjoint cells, so neither is redundant w.r.t. the other. Reusing the label-blind base logic 048 * would wrongly declare such a marker redundant; minor compaction would then drop it and resurrect 049 * cells that must stay deleted. {@link VisibilityScanDeleteTracker} must therefore conservatively 050 * report no delete as redundant. 051 */ 052@org.junit.jupiter.api.Tag(SecurityTests.TAG) 053@org.junit.jupiter.api.Tag(SmallTests.TAG) 054public class TestVisibilityScanDeleteTracker { 055 056 private static final byte[] ROW = Bytes.toBytes("row"); 057 private static final byte[] FAMILY = Bytes.toBytes("f"); 058 private static final byte[] QUALIFIER = Bytes.toBytes("q"); 059 060 // Placeholder visibility-tag payloads. The exact bytes are irrelevant here: the tracker only 061 // needs the tag to be of type VISIBILITY_TAG_TYPE, and the two markers to carry different ones. 062 private static final byte[] LABEL_A = new byte[] { 1 }; 063 private static final byte[] LABEL_B = new byte[] { 2 }; 064 065 /** 066 * A column-level delete must not be considered redundant just because a same-qualifier 067 * DeleteColumn with a higher timestamp was already tracked: if they carry different labels they 068 * shadow disjoint cells. The base tracker's {@code coveredByColumn} branch would return true. 069 */ 070 @Test 071 public void differentLabelColumnDeleteIsNotRedundant() { 072 VisibilityScanDeleteTracker tracker = 073 new VisibilityScanDeleteTracker(CellComparatorImpl.COMPARATOR); 074 // Tracked first (newest ts, as a compaction scan would present it). 075 tracker.add(deleteMarker(QUALIFIER, 100L, KeyValue.Type.DeleteColumn, LABEL_A)); 076 077 assertFalse( 078 tracker.isRedundantDelete(deleteMarker(QUALIFIER, 50L, KeyValue.Type.DeleteColumn, LABEL_B))); 079 assertFalse( 080 tracker.isRedundantDelete(deleteMarker(QUALIFIER, 50L, KeyValue.Type.Delete, LABEL_B))); 081 } 082 083 /** 084 * A family-level delete must not be considered redundant just because a DeleteFamily with a 085 * higher timestamp set the family stamp: a label-less family delete only shadows label-less 086 * cells, while a labeled one shadows that label's cells. The base tracker's 087 * {@code coveredByFamily} branch would return true. 088 */ 089 @Test 090 public void differentLabelFamilyDeleteIsNotRedundant() { 091 VisibilityScanDeleteTracker tracker = 092 new VisibilityScanDeleteTracker(CellComparatorImpl.COMPARATOR); 093 // A label-less DeleteFamily is the only thing that advances familyStamp (see add()). 094 tracker.add(deleteMarker(null, 100L, KeyValue.Type.DeleteFamily, null)); 095 096 assertFalse( 097 tracker.isRedundantDelete(deleteMarker(null, 50L, KeyValue.Type.DeleteFamily, LABEL_B))); 098 assertFalse(tracker 099 .isRedundantDelete(deleteMarker(null, 50L, KeyValue.Type.DeleteFamilyVersion, LABEL_B))); 100 } 101 102 /** 103 * Even a same-label marker is kept: the base {@code coveredByColumn} branch would (safely) treat 104 * a same-qualifier, same-label DeleteColumn of lower timestamp as redundant, but the visibility 105 * tracker forgoes that optimization so the label-blind base logic can never run. This pins the 106 * deliberately conservative contract against a future change that re-enables it. 107 */ 108 @Test 109 public void sameLabelColumnDeleteIsNotRedundant() { 110 VisibilityScanDeleteTracker tracker = 111 new VisibilityScanDeleteTracker(CellComparatorImpl.COMPARATOR); 112 tracker.add(deleteMarker(QUALIFIER, 100L, KeyValue.Type.DeleteColumn, LABEL_A)); 113 114 assertFalse( 115 tracker.isRedundantDelete(deleteMarker(QUALIFIER, 50L, KeyValue.Type.DeleteColumn, LABEL_A))); 116 } 117 118 /** 119 * Even where the base answer would actually be correct (a label-less DeleteColumn covered by a 120 * label-less one of higher timestamp), the tracker still reports no redundancy, keeping the 121 * contract uniform across labeled and label-less markers. 122 */ 123 @Test 124 public void labelLessColumnDeleteIsNotRedundant() { 125 VisibilityScanDeleteTracker tracker = 126 new VisibilityScanDeleteTracker(CellComparatorImpl.COMPARATOR); 127 tracker.add(deleteMarker(QUALIFIER, 100L, KeyValue.Type.DeleteColumn, null)); 128 129 assertFalse( 130 tracker.isRedundantDelete(deleteMarker(QUALIFIER, 50L, KeyValue.Type.DeleteColumn, null))); 131 } 132 133 private static ExtendedCell deleteMarker(byte[] qualifier, long ts, KeyValue.Type type, 134 byte[] label) { 135 List<Tag> tags = label == null 136 ? null 137 : Collections.singletonList(new ArrayBackedTag(TagType.VISIBILITY_TAG_TYPE, label)); 138 return new KeyValue(ROW, FAMILY, qualifier, ts, type, null, tags); 139 } 140}