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;
023import static org.mockito.Mockito.mock;
024
025import java.io.File;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.List;
031import java.util.UUID;
032import java.util.stream.Stream;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.fs.FSDataOutputStream;
035import org.apache.hadoop.fs.Path;
036import org.apache.hadoop.hbase.CellBuilderType;
037import org.apache.hadoop.hbase.CellUtil;
038import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
039import org.apache.hadoop.hbase.HBaseConfiguration;
040import org.apache.hadoop.hbase.HBaseTestingUtil;
041import org.apache.hadoop.hbase.KeyValue;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
044import org.apache.hadoop.hbase.client.RegionInfo;
045import org.apache.hadoop.hbase.client.RegionInfoBuilder;
046import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
047import org.apache.hadoop.hbase.io.hfile.HFile;
048import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
049import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
050import org.apache.hadoop.hbase.util.Bytes;
051import org.apache.hadoop.hbase.util.Pair;
052import org.apache.hadoop.hbase.wal.WAL;
053import org.apache.hadoop.hbase.wal.WALEdit;
054import org.hamcrest.Description;
055import org.hamcrest.Matcher;
056import org.hamcrest.TypeSafeMatcher;
057import org.junit.jupiter.api.BeforeEach;
058import org.junit.jupiter.api.TestInfo;
059import org.junit.jupiter.api.io.TempDir;
060import org.junit.jupiter.params.provider.Arguments;
061
062import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
063import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos;
064
065public class TestBulkloadBase {
066
067  @TempDir
068  public static File testFolder;
069  private static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
070  protected final WAL log = mock(WAL.class);
071  protected final Configuration conf = HBaseConfiguration.create();
072  private final byte[] randomBytes = new byte[100];
073  protected final byte[] family1 = Bytes.toBytes("family1");
074  protected final byte[] family2 = Bytes.toBytes("family2");
075  protected final byte[] family3 = Bytes.toBytes("family3");
076
077  protected Boolean useFileBasedSFT;
078  private String name;
079
080  public TestBulkloadBase(boolean useFileBasedSFT) {
081    this.useFileBasedSFT = useFileBasedSFT;
082  }
083
084  public static Stream<Arguments> parameters() {
085    return Stream.of(Arguments.of(false), Arguments.of(true));
086  }
087
088  @BeforeEach
089  public void before(TestInfo testInfo) throws IOException {
090    this.name = testInfo.getTestMethod().get().getName();
091    Bytes.random(randomBytes);
092    if (useFileBasedSFT) {
093      conf.set(StoreFileTrackerFactory.TRACKER_IMPL,
094        "org.apache.hadoop.hbase.regionserver.storefiletracker.FileBasedStoreFileTracker");
095    } else {
096      conf.unset(StoreFileTrackerFactory.TRACKER_IMPL);
097    }
098  }
099
100  protected Pair<byte[], String> withMissingHFileForFamily(byte[] family) {
101    return new Pair<>(family, getNotExistFilePath());
102  }
103
104  private String getNotExistFilePath() {
105    Path path = new Path(TEST_UTIL.getDataTestDir(), "does_not_exist");
106    return path.toUri().getPath();
107  }
108
109  protected Pair<byte[], String> withInvalidColumnFamilyButProperHFileLocation(byte[] family)
110    throws IOException {
111    createHFileForFamilies(family);
112    return new Pair<>(new byte[] { 0x00, 0x01, 0x02 }, getNotExistFilePath());
113  }
114
115  protected HRegion testRegionWithFamiliesAndSpecifiedTableName(TableName tableName,
116    byte[]... families) throws IOException {
117    RegionInfo hRegionInfo = RegionInfoBuilder.newBuilder(tableName).build();
118    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
119
120    for (byte[] family : families) {
121      builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
122    }
123    ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null,
124      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
125    // TODO We need a way to do this without creating files
126    return HRegion.createHRegion(hRegionInfo,
127      new Path(new File(testFolder, generateUniqueName(null)).toURI()), conf, builder.build(), log);
128
129  }
130
131  protected HRegion testRegionWithFamilies(byte[]... families) throws IOException {
132    TableName tableName = TableName.valueOf(name);
133    return testRegionWithFamiliesAndSpecifiedTableName(tableName, families);
134  }
135
136  private List<Pair<byte[], String>> getBlankFamilyPaths() {
137    return new ArrayList<>();
138  }
139
140  protected List<Pair<byte[], String>> withFamilyPathsFor(byte[]... families) throws IOException {
141    List<Pair<byte[], String>> familyPaths = getBlankFamilyPaths();
142    for (byte[] family : families) {
143      familyPaths.add(new Pair<>(family, createHFileForFamilies(family)));
144    }
145    return familyPaths;
146  }
147
148  private String createHFileForFamilies(byte[] family) throws IOException {
149    HFile.WriterFactory hFileFactory = HFile.getWriterFactoryNoCache(conf);
150    // TODO We need a way to do this without creating files
151    File hFileLocation = new File(testFolder, generateUniqueName(null));
152    FSDataOutputStream out = new FSDataOutputStream(new FileOutputStream(hFileLocation), null);
153    try {
154      hFileFactory.withOutputStream(out);
155      hFileFactory.withFileContext(new HFileContextBuilder().build());
156      HFile.Writer writer = hFileFactory.create();
157      try {
158        writer.append(new KeyValue(ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY)
159          .setRow(randomBytes).setFamily(family).setQualifier(randomBytes).setTimestamp(0L)
160          .setType(KeyValue.Type.Put.getCode()).setValue(randomBytes).build()));
161      } finally {
162        writer.close();
163      }
164    } finally {
165      out.close();
166    }
167    return hFileLocation.getAbsoluteFile().getAbsolutePath();
168  }
169
170  protected static String generateUniqueName(final String suffix) {
171    String name = UUID.randomUUID().toString().replaceAll("-", "");
172    if (suffix != null) name += suffix;
173    return name;
174  }
175
176  protected static Matcher<WALEdit> bulkLogWalEditType(byte[] typeBytes) {
177    return new WalMatcher(typeBytes);
178  }
179
180  protected static Matcher<WALEdit> bulkLogWalEdit(byte[] typeBytes, byte[] tableName,
181    byte[] familyName, List<String> storeFileNames) {
182    return new WalMatcher(typeBytes, tableName, familyName, storeFileNames);
183  }
184
185  private static class WalMatcher extends TypeSafeMatcher<WALEdit> {
186    private final byte[] typeBytes;
187    private final byte[] tableName;
188    private final byte[] familyName;
189    private final List<String> storeFileNames;
190
191    public WalMatcher(byte[] typeBytes) {
192      this(typeBytes, null, null, null);
193    }
194
195    public WalMatcher(byte[] typeBytes, byte[] tableName, byte[] familyName,
196      List<String> storeFileNames) {
197      this.typeBytes = typeBytes;
198      this.tableName = tableName;
199      this.familyName = familyName;
200      this.storeFileNames = storeFileNames;
201    }
202
203    @Override
204    protected boolean matchesSafely(WALEdit item) {
205      assertTrue(Arrays.equals(CellUtil.cloneQualifier(item.getCells().get(0)), typeBytes));
206      WALProtos.BulkLoadDescriptor desc;
207      try {
208        desc = WALEdit.getBulkLoadDescriptor(item.getCells().get(0));
209      } catch (IOException e) {
210        return false;
211      }
212      assertNotNull(desc);
213
214      if (tableName != null) {
215        assertTrue(
216          Bytes.equals(ProtobufUtil.toTableName(desc.getTableName()).getName(), tableName));
217      }
218
219      if (storeFileNames != null) {
220        int index = 0;
221        WALProtos.StoreDescriptor store = desc.getStores(0);
222        assertTrue(Bytes.equals(store.getFamilyName().toByteArray(), familyName));
223        assertTrue(Bytes.equals(Bytes.toBytes(store.getStoreHomeDir()), familyName));
224        assertEquals(storeFileNames.size(), store.getStoreFileCount());
225      }
226
227      return true;
228    }
229
230    @Override
231    public void describeTo(Description description) {
232
233    }
234  }
235}