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.mapreduce; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022 023import java.util.ArrayList; 024import java.util.List; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileStatus; 027import org.apache.hadoop.fs.LocatedFileStatus; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.regionserver.HRegionServer; 032import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL; 033import org.apache.hadoop.hbase.testclassification.MapReduceTests; 034import org.apache.hadoop.hbase.testclassification.MediumTests; 035import org.apache.hadoop.hbase.util.CommonFSUtils; 036import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 037import org.apache.hadoop.mapreduce.InputSplit; 038import org.apache.hadoop.mapreduce.Job; 039import org.apache.hadoop.mapreduce.JobContext; 040import org.apache.hadoop.mapreduce.TaskAttemptContext; 041import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; 042import org.junit.jupiter.api.BeforeAll; 043import org.junit.jupiter.api.Tag; 044import org.junit.jupiter.api.Test; 045import org.mockito.Mockito; 046 047@Tag(MapReduceTests.TAG) 048@Tag(MediumTests.TAG) 049public class TestWALInputFormat { 050 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 051 052 @BeforeAll 053 public static void setupClass() throws Exception { 054 TEST_UTIL.startMiniCluster(); 055 TEST_UTIL.createWALRootDir(); 056 } 057 058 /** 059 * Test the primitive start/end time filtering. 060 */ 061 @Test 062 public void testAddFile() { 063 List<FileStatus> lfss = new ArrayList<>(); 064 LocatedFileStatus lfs = Mockito.mock(LocatedFileStatus.class); 065 long now = EnvironmentEdgeManager.currentTime(); 066 Mockito.when(lfs.getPath()).thenReturn(new Path("/name." + now)); 067 WALInputFormat.addFile(lfss, lfs, now, now); 068 assertEquals(1, lfss.size()); 069 WALInputFormat.addFile(lfss, lfs, now - 1, now - 1); 070 assertEquals(1, lfss.size()); 071 WALInputFormat.addFile(lfss, lfs, now - 2, now - 1); 072 assertEquals(1, lfss.size()); 073 WALInputFormat.addFile(lfss, lfs, now - 2, now); 074 assertEquals(2, lfss.size()); 075 WALInputFormat.addFile(lfss, lfs, Long.MIN_VALUE, now); 076 assertEquals(3, lfss.size()); 077 WALInputFormat.addFile(lfss, lfs, Long.MIN_VALUE, Long.MAX_VALUE); 078 assertEquals(4, lfss.size()); 079 WALInputFormat.addFile(lfss, lfs, now, now + 2); 080 assertEquals(5, lfss.size()); 081 WALInputFormat.addFile(lfss, lfs, now + 1, now + 2); 082 assertEquals(5, lfss.size()); 083 Mockito.when(lfs.getPath()).thenReturn(new Path("/name")); 084 WALInputFormat.addFile(lfss, lfs, Long.MIN_VALUE, Long.MAX_VALUE); 085 assertEquals(6, lfss.size()); 086 Mockito.when(lfs.getPath()).thenReturn(new Path("/name.123")); 087 WALInputFormat.addFile(lfss, lfs, Long.MIN_VALUE, Long.MAX_VALUE); 088 assertEquals(7, lfss.size()); 089 Mockito.when(lfs.getPath()).thenReturn(new Path("/name." + now + ".meta")); 090 WALInputFormat.addFile(lfss, lfs, now, now); 091 assertEquals(8, lfss.size()); 092 } 093 094 @Test 095 public void testHandlesArchivedWALFiles() throws Exception { 096 Configuration conf = TEST_UTIL.getConfiguration(); 097 JobContext ctx = Mockito.mock(JobContext.class); 098 Mockito.when(ctx.getConfiguration()).thenReturn(conf); 099 Job job = Job.getInstance(conf); 100 TableMapReduceUtil.initCredentialsForCluster(job, conf); 101 Mockito.when(ctx.getCredentials()).thenReturn(job.getCredentials()); 102 103 // Setup WAL file, then archive it 104 HRegionServer rs = TEST_UTIL.getHBaseCluster().getRegionServer(0); 105 AbstractFSWAL wal = (AbstractFSWAL) rs.getWALs().get(0); 106 Path walPath = wal.getCurrentFileName(); 107 TEST_UTIL.getConfiguration().set(FileInputFormat.INPUT_DIR, walPath.toString()); 108 TEST_UTIL.getConfiguration().set(WALPlayer.INPUT_FILES_SEPARATOR_KEY, ";"); 109 110 Path rootDir = CommonFSUtils.getWALRootDir(conf); 111 Path archiveWal = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME); 112 archiveWal = new Path(archiveWal, walPath.getName()); 113 TEST_UTIL.getTestFileSystem().delete(walPath, true); 114 TEST_UTIL.getTestFileSystem().mkdirs(archiveWal.getParent()); 115 TEST_UTIL.getTestFileSystem().create(archiveWal).close(); 116 117 // Test for that we can read from the archived WAL file 118 WALInputFormat wif = new WALInputFormat(); 119 List<InputSplit> splits = wif.getSplits(ctx); 120 assertEquals(1, splits.size()); 121 WALInputFormat.WALSplit split = (WALInputFormat.WALSplit) splits.get(0); 122 assertEquals(archiveWal.toString(), split.getLogFileName()); 123 } 124 125 /** 126 * Test that an empty WAL file (which causes WALHeaderEOFException) is gracefully handled and 127 * skipped rather than causing the job to fail. 128 */ 129 @Test 130 public void testHandlesEmptyWALFile() throws Exception { 131 Configuration conf = TEST_UTIL.getConfiguration(); 132 133 // Create an empty WAL file 134 Path walRootDir = CommonFSUtils.getWALRootDir(conf); 135 Path emptyWalFile = 136 new Path(walRootDir, "WALs/empty-wal-test/empty." + EnvironmentEdgeManager.currentTime()); 137 TEST_UTIL.getTestFileSystem().mkdirs(emptyWalFile.getParent()); 138 TEST_UTIL.getTestFileSystem().create(emptyWalFile).close(); 139 140 try { 141 JobContext ctx = Mockito.mock(JobContext.class); 142 conf.set(FileInputFormat.INPUT_DIR, emptyWalFile.toString()); 143 conf.set(WALPlayer.INPUT_FILES_SEPARATOR_KEY, ";"); 144 Mockito.when(ctx.getConfiguration()).thenReturn(conf); 145 Job job = Job.getInstance(conf); 146 TableMapReduceUtil.initCredentialsForCluster(job, conf); 147 Mockito.when(ctx.getCredentials()).thenReturn(job.getCredentials()); 148 149 // Create record reader and verify it handles the empty file gracefully 150 try (WALInputFormat.WALKeyRecordReader reader = new WALInputFormat.WALKeyRecordReader()) { 151 TaskAttemptContext taskCtx = Mockito.mock(TaskAttemptContext.class); 152 Mockito.when(taskCtx.getConfiguration()).thenReturn(conf); 153 154 WALInputFormat wif = new WALInputFormat(); 155 List<InputSplit> splits = wif.getSplits(ctx); 156 assertEquals(1, splits.size()); 157 WALInputFormat.WALSplit split = (WALInputFormat.WALSplit) splits.get(0); 158 159 // This should not throw WALHeaderEOFException - it should return false for nextKeyValue() 160 reader.initialize(split, taskCtx); 161 // nextKeyValue() should return false since the file is empty (reader is null) 162 assertFalse(reader.nextKeyValue()); 163 } 164 } finally { 165 TEST_UTIL.getTestFileSystem().delete(emptyWalFile.getParent(), true); 166 } 167 } 168 169}