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.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory.TRACKER_IMPL;
021import static org.hamcrest.MatcherAssert.assertThat;
022import static org.hamcrest.Matchers.containsString;
023import static org.hamcrest.Matchers.everyItem;
024import static org.hamcrest.Matchers.hasItem;
025import static org.hamcrest.Matchers.hasProperty;
026import static org.hamcrest.Matchers.not;
027import static org.junit.jupiter.api.Assertions.assertTrue;
028
029import java.io.IOException;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.UUID;
033import java.util.concurrent.TimeUnit;
034import org.apache.hadoop.fs.FileStatus;
035import org.apache.hadoop.fs.FileSystem;
036import org.apache.hadoop.fs.FileUtil;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.hbase.HBaseTestingUtil;
039import org.apache.hadoop.hbase.TableName;
040import org.apache.hadoop.hbase.TableNameTestExtension;
041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
042import org.apache.hadoop.hbase.client.Put;
043import org.apache.hadoop.hbase.client.RegionInfo;
044import org.apache.hadoop.hbase.client.RegionInfoBuilder;
045import org.apache.hadoop.hbase.client.Table;
046import org.apache.hadoop.hbase.client.TableDescriptor;
047import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
048import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
049import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
050import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
051import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerForTest;
052import org.apache.hadoop.hbase.testclassification.LargeTests;
053import org.apache.hadoop.hbase.testclassification.RegionServerTests;
054import org.apache.hadoop.hbase.util.Bytes;
055import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
056import org.apache.hadoop.hbase.util.Pair;
057import org.junit.jupiter.api.AfterAll;
058import org.junit.jupiter.api.BeforeAll;
059import org.junit.jupiter.api.BeforeEach;
060import org.junit.jupiter.api.Tag;
061import org.junit.jupiter.api.Test;
062import org.junit.jupiter.api.extension.RegisterExtension;
063
064@Tag(RegionServerTests.TAG)
065@Tag(LargeTests.TAG)
066public class TestMergesSplitsAddToTracker {
067
068  private static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
069
070  private static final String FAMILY_NAME_STR = "info";
071
072  private static final byte[] FAMILY_NAME = Bytes.toBytes(FAMILY_NAME_STR);
073
074  @RegisterExtension
075  public TableNameTestExtension name = new TableNameTestExtension();
076
077  @BeforeAll
078  public static void setupClass() throws Exception {
079    TEST_UTIL.startMiniCluster();
080  }
081
082  @AfterAll
083  public static void afterClass() throws Exception {
084    TEST_UTIL.shutdownMiniCluster();
085  }
086
087  @BeforeEach
088  public void setup() {
089    StoreFileTrackerForTest.clear();
090  }
091
092  private TableName createTable(byte[] splitKey) throws IOException {
093    TableDescriptor td = TableDescriptorBuilder.newBuilder(name.getTableName())
094      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_NAME))
095      .setValue(TRACKER_IMPL, StoreFileTrackerForTest.class.getName()).build();
096    if (splitKey != null) {
097      TEST_UTIL.getAdmin().createTable(td, new byte[][] { splitKey });
098    } else {
099      TEST_UTIL.getAdmin().createTable(td);
100    }
101    return td.getTableName();
102  }
103
104  @Test
105  public void testCommitDaughterRegion() throws Exception {
106    TableName table = createTable(null);
107    // first put some data in order to have a store file created
108    putThreeRowsAndFlush(table);
109    HRegion region = TEST_UTIL.getHBaseCluster().getRegions(table).get(0);
110    HRegionFileSystem regionFS = region.getStores().get(0).getRegionFileSystem();
111    RegionInfo daughterA =
112      RegionInfoBuilder.newBuilder(table).setStartKey(region.getRegionInfo().getStartKey())
113        .setEndKey(Bytes.toBytes("002")).setSplit(false)
114        .setRegionId(region.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime())
115        .build();
116    RegionInfo daughterB = RegionInfoBuilder.newBuilder(table).setStartKey(Bytes.toBytes("002"))
117      .setEndKey(region.getRegionInfo().getEndKey()).setSplit(false)
118      .setRegionId(region.getRegionInfo().getRegionId()).build();
119    HStoreFile file = (HStoreFile) region.getStore(FAMILY_NAME).getStorefiles().toArray()[0];
120    List<StoreFileInfo> splitFilesA = new ArrayList<>();
121    HRegionFileSystem regionFs = region.getRegionFileSystem();
122    StoreFileTracker sft = StoreFileTrackerFactory.create(region.getBaseConf(), true,
123      StoreContext.getBuilder()
124        .withFamilyStoreDirectoryPath(new Path(regionFs.getRegionDir(), "info"))
125        .withRegionFileSystem(regionFs).build());
126    splitFilesA.add(regionFS.splitStoreFile(daughterA, Bytes.toString(FAMILY_NAME), file,
127      Bytes.toBytes("002"), false, region.getSplitPolicy(), sft));
128    List<StoreFileInfo> splitFilesB = new ArrayList<>();
129    splitFilesB.add(regionFS.splitStoreFile(daughterB, Bytes.toString(FAMILY_NAME), file,
130      Bytes.toBytes("002"), true, region.getSplitPolicy(), sft));
131    MasterProcedureEnv env =
132      TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment();
133    Path resultA = regionFS.commitDaughterRegion(daughterA, splitFilesA, env);
134    Path resultB = regionFS.commitDaughterRegion(daughterB, splitFilesB, env);
135    FileSystem fs = regionFS.getFileSystem();
136    verifyFilesAreTracked(resultA, fs);
137    verifyFilesAreTracked(resultB, fs);
138  }
139
140  @Test
141  public void testCommitMergedRegion() throws Exception {
142    TableName table = createTable(null);
143    // splitting the table first
144    TEST_UTIL.getAdmin().split(table, Bytes.toBytes("002"));
145    // Add data and flush to create files in the two different regions
146    putThreeRowsAndFlush(table);
147    List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(table);
148    HRegion first = regions.get(0);
149    HRegion second = regions.get(1);
150    HRegionFileSystem regionFS = first.getRegionFileSystem();
151
152    RegionInfo mergeResult =
153      RegionInfoBuilder.newBuilder(table).setStartKey(first.getRegionInfo().getStartKey())
154        .setEndKey(second.getRegionInfo().getEndKey()).setSplit(false)
155        .setRegionId(first.getRegionInfo().getRegionId() + EnvironmentEdgeManager.currentTime())
156        .build();
157
158    HRegionFileSystem mergeFS = HRegionFileSystem.createRegionOnFileSystem(
159      TEST_UTIL.getHBaseCluster().getMaster().getConfiguration(), regionFS.getFileSystem(),
160      regionFS.getTableDir(), mergeResult);
161
162    List<StoreFileInfo> mergedFiles = new ArrayList<>();
163    // merge file from first region
164    mergedFiles.add(mergeFileFromRegion(first, mergeFS));
165    // merge file from second region
166    mergedFiles.add(mergeFileFromRegion(second, mergeFS));
167    MasterProcedureEnv env =
168      TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment();
169    mergeFS.commitMergedRegion(mergedFiles, env);
170    // validate
171    FileSystem fs = first.getRegionFileSystem().getFileSystem();
172    Path finalMergeDir =
173      new Path(first.getRegionFileSystem().getTableDir(), mergeResult.getEncodedName());
174    verifyFilesAreTracked(finalMergeDir, fs);
175  }
176
177  @Test
178  public void testSplitLoadsFromTracker() throws Exception {
179    TableName table = createTable(null);
180    // Add data and flush to create files in the two different regions
181    putThreeRowsAndFlush(table);
182    HRegion region = TEST_UTIL.getHBaseCluster().getRegions(table).get(0);
183    Pair<StoreFileInfo, String> copyResult = copyFileInTheStoreDir(region);
184    StoreFileInfo fileInfo = copyResult.getFirst();
185    String copyName = copyResult.getSecond();
186    // Now splits the region
187    TEST_UTIL.getAdmin().split(table, Bytes.toBytes("002"));
188    List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(table);
189    HRegion first = regions.get(0);
190    validateDaughterRegionsFiles(first, fileInfo.getActiveFileName(), copyName);
191    HRegion second = regions.get(1);
192    validateDaughterRegionsFiles(second, fileInfo.getActiveFileName(), copyName);
193  }
194
195  @Test
196  public void testMergeLoadsFromTracker() throws Exception {
197    TableName table = createTable(Bytes.toBytes("002"));
198    // Add data and flush to create files in the two different regions
199    putThreeRowsAndFlush(table);
200    List<HRegion> regions = TEST_UTIL.getHBaseCluster().getRegions(table);
201    HRegion first = regions.get(0);
202    Pair<StoreFileInfo, String> copyResult = copyFileInTheStoreDir(first);
203    StoreFileInfo fileInfo = copyResult.getFirst();
204    String copyName = copyResult.getSecond();
205    // Now merges the first two regions
206    TEST_UTIL.getAdmin()
207      .mergeRegionsAsync(new byte[][] { first.getRegionInfo().getEncodedNameAsBytes(),
208        regions.get(1).getRegionInfo().getEncodedNameAsBytes() }, true)
209      .get(10, TimeUnit.SECONDS);
210    regions = TEST_UTIL.getHBaseCluster().getRegions(table);
211    HRegion merged = regions.get(0);
212    validateDaughterRegionsFiles(merged, fileInfo.getActiveFileName(), copyName);
213  }
214
215  private Pair<StoreFileInfo, String> copyFileInTheStoreDir(HRegion region) throws IOException {
216    Path storeDir = region.getRegionFileSystem().getStoreDir("info");
217    // gets the single file
218    HRegionFileSystem regionFs = region.getRegionFileSystem();
219    StoreFileTracker sft = StoreFileTrackerFactory.create(region.getBaseConf(), false,
220      StoreContext.getBuilder().withFamilyStoreDirectoryPath(storeDir)
221        .withColumnFamilyDescriptor(ColumnFamilyDescriptorBuilder.of(FAMILY_NAME))
222        .withRegionFileSystem(regionFs).build());
223    List<StoreFileInfo> infos = sft.load();
224    StoreFileInfo fileInfo = infos.get(0);
225    // make a copy of the valid file staight into the store dir, so that it's not tracked.
226    String copyName = UUID.randomUUID().toString().replaceAll("-", "");
227    Path copy = new Path(storeDir, copyName);
228    FileUtil.copy(region.getFilesystem(), fileInfo.getFileStatus(), region.getFilesystem(), copy,
229      false, false, TEST_UTIL.getConfiguration());
230    return new Pair<>(fileInfo, copyName);
231  }
232
233  private void validateDaughterRegionsFiles(HRegion region, String originalFileName,
234    String untrackedFile) throws IOException {
235    // verify there's no link for the untracked, copied file in first region
236    HRegionFileSystem regionFs = region.getRegionFileSystem();
237    StoreFileTracker sft = StoreFileTrackerFactory.create(regionFs.getFileSystem().getConf(), false,
238      StoreContext.getBuilder()
239        .withFamilyStoreDirectoryPath(new Path(regionFs.getRegionDir(), "info"))
240        .withColumnFamilyDescriptor(ColumnFamilyDescriptorBuilder.of(FAMILY_NAME))
241        .withRegionFileSystem(regionFs).build());
242    List<StoreFileInfo> infos = sft.load();
243    assertThat(infos, everyItem(hasProperty("activeFileName", not(containsString(untrackedFile)))));
244    assertThat(infos, hasItem(hasProperty("activeFileName", containsString(originalFileName))));
245  }
246
247  private void verifyFilesAreTracked(Path regionDir, FileSystem fs) throws Exception {
248    for (FileStatus f : fs.listStatus(new Path(regionDir, FAMILY_NAME_STR))) {
249      assertTrue(
250        StoreFileTrackerForTest.tracked(regionDir.getName(), FAMILY_NAME_STR, f.getPath()));
251    }
252  }
253
254  private StoreFileInfo mergeFileFromRegion(HRegion regionToMerge, HRegionFileSystem mergeFS)
255    throws IOException {
256    HStoreFile file = (HStoreFile) regionToMerge.getStore(FAMILY_NAME).getStorefiles().toArray()[0];
257    HRegionFileSystem regionFs = regionToMerge.getRegionFileSystem();
258    StoreFileTracker sft = StoreFileTrackerFactory.create(regionToMerge.getBaseConf(), true,
259      StoreContext.getBuilder()
260        .withFamilyStoreDirectoryPath(new Path(regionFs.getRegionDir(), FAMILY_NAME_STR))
261        .withRegionFileSystem(regionFs).build());
262    return mergeFS.mergeStoreFile(regionToMerge.getRegionInfo(), Bytes.toString(FAMILY_NAME), file,
263      sft);
264  }
265
266  private void putThreeRowsAndFlush(TableName table) throws IOException {
267    Table tbl = TEST_UTIL.getConnection().getTable(table);
268    Put put = new Put(Bytes.toBytes("001"));
269    byte[] qualifier = Bytes.toBytes("1");
270    put.addColumn(FAMILY_NAME, qualifier, Bytes.toBytes(1));
271    tbl.put(put);
272    put = new Put(Bytes.toBytes("002"));
273    put.addColumn(FAMILY_NAME, qualifier, Bytes.toBytes(2));
274    tbl.put(put);
275    put = new Put(Bytes.toBytes("003"));
276    put.addColumn(FAMILY_NAME, qualifier, Bytes.toBytes(2));
277    tbl.put(put);
278    TEST_UTIL.flush(table);
279  }
280}