Skip to content

Commit b08697a

Browse files
authored
HBASE-23197 'IllegalArgumentException: Wrong FS' on edits replay when… (#740)
Signed-off-by: Josh Elser <[email protected]>
1 parent b1df7df commit b08697a

File tree

7 files changed

+281
-10
lines changed

7 files changed

+281
-10
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/backup/HFileArchiver.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@
3838
import org.apache.hadoop.fs.FileSystem;
3939
import org.apache.hadoop.fs.Path;
4040
import org.apache.hadoop.fs.PathFilter;
41+
import org.apache.hadoop.hbase.HConstants;
4142
import org.apache.hadoop.hbase.client.RegionInfo;
4243
import org.apache.hadoop.hbase.regionserver.HStoreFile;
4344
import org.apache.hadoop.hbase.util.Bytes;
45+
import org.apache.hadoop.hbase.util.CommonFSUtils;
4446
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
4547
import org.apache.hadoop.hbase.util.FSUtils;
4648
import org.apache.hadoop.hbase.util.HFileArchiveUtil;
@@ -295,8 +297,45 @@ public static void archiveFamilyByFamilyDir(FileSystem fs, Configuration conf,
295297
*/
296298
public static void archiveStoreFiles(Configuration conf, FileSystem fs, RegionInfo regionInfo,
297299
Path tableDir, byte[] family, Collection<HStoreFile> compactedFiles)
298-
throws IOException, FailedArchiveException {
300+
throws IOException {
301+
Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, regionInfo, tableDir, family);
302+
archive(fs, regionInfo, family, compactedFiles, storeArchiveDir);
303+
}
304+
305+
/**
306+
* Archive recovered edits using existing logic for archiving store files. This is currently only
307+
* relevant when <b>hbase.region.archive.recovered.edits</b> is true, as recovered edits shouldn't
308+
* be kept after replay. In theory, we could use very same method available for archiving
309+
* store files, but supporting WAL dir and store files on different FileSystems added the need for
310+
* extra validation of the passed FileSystem instance and the path where the archiving edits
311+
* should be placed.
312+
* @param conf {@link Configuration} to determine the archive directory.
313+
* @param fs the filesystem used for storing WAL files.
314+
* @param regionInfo {@link RegionInfo} a pseudo region representation for the archiving logic.
315+
* @param family a pseudo familiy representation for the archiving logic.
316+
* @param replayedEdits the recovered edits to be archived.
317+
* @throws IOException if files can't be achived due to some internal error.
318+
*/
319+
public static void archiveRecoveredEdits(Configuration conf, FileSystem fs, RegionInfo regionInfo,
320+
byte[] family, Collection<HStoreFile> replayedEdits)
321+
throws IOException {
322+
String workingDir = conf.get(CommonFSUtils.HBASE_WAL_DIR, conf.get(HConstants.HBASE_DIR));
323+
//extra sanity checks for the right FS
324+
Path path = new Path(workingDir);
325+
if(path.isAbsoluteAndSchemeAuthorityNull()){
326+
//no schema specified on wal dir value, so it's on same FS as StoreFiles
327+
path = new Path(conf.get(HConstants.HBASE_DIR));
328+
}
329+
if(path.toUri().getScheme()!=null && !path.toUri().getScheme().equals(fs.getScheme())){
330+
throw new IOException("Wrong file system! Should be " + path.toUri().getScheme() +
331+
", but got " + fs.getScheme());
332+
}
333+
path = HFileArchiveUtil.getStoreArchivePathForRootDir(path, regionInfo, family);
334+
archive(fs, regionInfo, family, replayedEdits, path);
335+
}
299336

337+
private static void archive(FileSystem fs, RegionInfo regionInfo, byte[] family,
338+
Collection<HStoreFile> compactedFiles, Path storeArchiveDir) throws IOException {
300339
// sometimes in testing, we don't have rss, so we need to check for that
301340
if (fs == null) {
302341
LOG.warn("Passed filesystem is null, so just deleting files without archiving for {}," +
@@ -314,9 +353,6 @@ public static void archiveStoreFiles(Configuration conf, FileSystem fs, RegionIn
314353
// build the archive path
315354
if (regionInfo == null || family == null) throw new IOException(
316355
"Need to have a region and a family to archive from.");
317-
318-
Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, regionInfo, tableDir, family);
319-
320356
// make sure we don't archive if we can't and that the archive dir exists
321357
if (!fs.mkdirs(storeArchiveDir)) {
322358
throw new IOException("Could not make archive directory (" + storeArchiveDir + ") for store:"

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,8 +1957,8 @@ public HRegionFileSystem getRegionFileSystem() {
19571957
}
19581958

19591959
/** @return the WAL {@link HRegionFileSystem} used by this region */
1960-
HRegionFileSystem getRegionWALFileSystem() throws IOException {
1961-
return new HRegionFileSystem(conf, getWalFileSystem(),
1960+
HRegionWALFileSystem getRegionWALFileSystem() throws IOException {
1961+
return new HRegionWALFileSystem(conf, getWalFileSystem(),
19621962
FSUtils.getWALTableDir(conf, htableDescriptor.getTableName()), fs.getRegionInfo());
19631963
}
19641964

@@ -4681,7 +4681,7 @@ protected long replayRecoveredEditsIfAny(Map<byte[], Long> maxSeqIdInStores,
46814681
for (Path file : files) {
46824682
fakeStoreFiles.add(new HStoreFile(walFS, file, this.conf, null, null, true));
46834683
}
4684-
getRegionWALFileSystem().removeStoreFiles(fakeFamilyName, fakeStoreFiles);
4684+
getRegionWALFileSystem().archiveRecoveredEdits(fakeFamilyName, fakeStoreFiles);
46854685
} else {
46864686
for (Path file : Iterables.concat(files, filesUnderWrongRegionWALDir)) {
46874687
if (!walFS.delete(file, false)) {

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionFileSystem.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ public class HRegionFileSystem {
8080

8181
private final RegionInfo regionInfo;
8282
//regionInfo for interacting with FS (getting encodedName, etc)
83-
private final RegionInfo regionInfoForFs;
84-
private final Configuration conf;
83+
final RegionInfo regionInfoForFs;
84+
final Configuration conf;
8585
private final Path tableDir;
86-
private final FileSystem fs;
86+
final FileSystem fs;
8787
private final Path regionDir;
8888

8989
/**
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.regionserver;
19+
20+
import java.io.IOException;
21+
import java.util.Collection;
22+
import org.apache.hadoop.conf.Configuration;
23+
import org.apache.hadoop.fs.FileSystem;
24+
import org.apache.hadoop.fs.Path;
25+
import org.apache.hadoop.hbase.backup.HFileArchiver;
26+
import org.apache.hadoop.hbase.client.RegionInfo;
27+
import org.apache.hadoop.hbase.util.Bytes;
28+
import org.apache.yetus.audience.InterfaceAudience;
29+
30+
/**
31+
* A Wrapper for the region FileSystem operations adding WAL specific operations
32+
*/
33+
@InterfaceAudience.Private
34+
public class HRegionWALFileSystem extends HRegionFileSystem {
35+
36+
HRegionWALFileSystem(Configuration conf, FileSystem fs, Path tableDir, RegionInfo regionInfo) {
37+
super(conf, fs, tableDir, regionInfo);
38+
}
39+
40+
/**
41+
* Closes and archives the specified store files from the specified family.
42+
* @param familyName Family that contains the store filesMeta
43+
* @param storeFiles set of store files to remove
44+
* @throws IOException if the archiving fails
45+
*/
46+
public void archiveRecoveredEdits(String familyName, Collection<HStoreFile> storeFiles)
47+
throws IOException {
48+
HFileArchiver.archiveRecoveredEdits(this.conf, this.fs, this.regionInfoForFs,
49+
Bytes.toBytes(familyName), storeFiles);
50+
}
51+
}

hbase-server/src/main/java/org/apache/hadoop/hbase/util/HFileArchiveUtil.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ public static Path getStoreArchivePath(Configuration conf,
8585
return HStore.getStoreHomedir(tableArchiveDir, region, family);
8686
}
8787

88+
/**
89+
* Gets the archive directory under specified root dir. One scenario where this is useful is
90+
* when WAL and root dir are configured under different file systems,
91+
* i.e. root dir on S3 and WALs on HDFS.
92+
* This is mostly useful for archiving recovered edits, when
93+
* <b>hbase.region.archive.recovered.edits</b> is enabled.
94+
* @param rootDir {@link Path} the root dir under which archive path should be created.
95+
* @param region parent region information under which the store currently lives
96+
* @param family name of the family in the store
97+
* @return {@link Path} to the WAL FS directory to archive the given store
98+
* or <tt>null</tt> if it should not be archived
99+
*/
100+
public static Path getStoreArchivePathForRootDir(Path rootDir, RegionInfo region, byte[] family) {
101+
Path tableArchiveDir = getTableArchivePath(rootDir, region.getTable());
102+
return HStore.getStoreHomedir(tableArchiveDir, region, family);
103+
}
104+
88105
/**
89106
* Get the archive directory for a given region under the specified table
90107
* @param tableName the table name. Cannot be null.

hbase-server/src/test/java/org/apache/hadoop/hbase/backup/TestHFileArchiving.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@
2121
import static org.junit.Assert.assertFalse;
2222
import static org.junit.Assert.assertNotNull;
2323
import static org.junit.Assert.assertTrue;
24+
import static org.mockito.ArgumentMatchers.any;
25+
import static org.mockito.Mockito.mock;
26+
import static org.mockito.Mockito.times;
27+
import static org.mockito.Mockito.verify;
28+
import static org.mockito.Mockito.when;
2429

2530
import java.io.IOException;
2631
import java.security.PrivilegedExceptionAction;
2732
import java.util.ArrayList;
33+
import java.util.Collection;
2834
import java.util.Collections;
2935
import java.util.HashMap;
3036
import java.util.List;
@@ -43,16 +49,19 @@
4349
import org.apache.hadoop.hbase.Stoppable;
4450
import org.apache.hadoop.hbase.TableName;
4551
import org.apache.hadoop.hbase.client.Admin;
52+
import org.apache.hadoop.hbase.client.RegionInfo;
4653
import org.apache.hadoop.hbase.client.Table;
4754
import org.apache.hadoop.hbase.master.HMaster;
4855
import org.apache.hadoop.hbase.master.cleaner.DirScanPool;
4956
import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
5057
import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
5158
import org.apache.hadoop.hbase.regionserver.HRegion;
5259
import org.apache.hadoop.hbase.regionserver.HRegionServer;
60+
import org.apache.hadoop.hbase.regionserver.HStoreFile;
5361
import org.apache.hadoop.hbase.testclassification.MediumTests;
5462
import org.apache.hadoop.hbase.testclassification.MiscTests;
5563
import org.apache.hadoop.hbase.util.Bytes;
64+
import org.apache.hadoop.hbase.util.CommonFSUtils;
5665
import org.apache.hadoop.hbase.util.FSUtils;
5766
import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil;
5867
import org.apache.hadoop.hbase.util.HFileArchiveUtil;
@@ -67,6 +76,7 @@
6776
import org.junit.Test;
6877
import org.junit.experimental.categories.Category;
6978
import org.junit.rules.TestName;
79+
import org.mockito.ArgumentCaptor;
7080
import org.slf4j.Logger;
7181
import org.slf4j.LoggerFactory;
7282

@@ -128,6 +138,107 @@ public static void cleanupTest() throws Exception {
128138
POOL.shutdownNow();
129139
}
130140

141+
@Test
142+
public void testArchiveStoreFilesDifferentFileSystemsWallWithSchemaPlainRoot() throws Exception {
143+
String walDir = "mockFS://mockFSAuthority:9876/mockDir/wals/";
144+
String baseDir = FSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/";
145+
testArchiveStoreFilesDifferentFileSystems(walDir, baseDir,
146+
HFileArchiver::archiveStoreFiles);
147+
}
148+
149+
@Test
150+
public void testArchiveStoreFilesDifferentFileSystemsWallNullPlainRoot() throws Exception {
151+
String baseDir = FSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/";
152+
testArchiveStoreFilesDifferentFileSystems(null, baseDir,
153+
HFileArchiver::archiveStoreFiles);
154+
}
155+
156+
@Test
157+
public void testArchiveStoreFilesDifferentFileSystemsWallAndRootSame() throws Exception {
158+
String baseDir = FSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/";
159+
testArchiveStoreFilesDifferentFileSystems("/hbase/wals/", baseDir,
160+
HFileArchiver::archiveStoreFiles);
161+
}
162+
163+
private void testArchiveStoreFilesDifferentFileSystems(String walDir, String expectedBase,
164+
ArchivingFunction<Configuration, FileSystem, RegionInfo, Path, byte[],
165+
Collection<HStoreFile>> archivingFunction) throws IOException {
166+
FileSystem mockedFileSystem = mock(FileSystem.class);
167+
Configuration conf = new Configuration(UTIL.getConfiguration());
168+
if(walDir != null) {
169+
conf.set(CommonFSUtils.HBASE_WAL_DIR, walDir);
170+
}
171+
Path filePath = new Path("/mockDir/wals/mockFile");
172+
when(mockedFileSystem.getScheme()).thenReturn("mockFS");
173+
when(mockedFileSystem.mkdirs(any())).thenReturn(true);
174+
when(mockedFileSystem.exists(any())).thenReturn(true);
175+
RegionInfo mockedRegion = mock(RegionInfo.class);
176+
TableName tableName = TableName.valueOf("mockTable");
177+
when(mockedRegion.getTable()).thenReturn(tableName);
178+
when(mockedRegion.getEncodedName()).thenReturn("mocked-region-encoded-name");
179+
Path tableDir = new Path("mockFS://mockDir/tabledir");
180+
byte[] family = Bytes.toBytes("testfamily");
181+
HStoreFile mockedFile = mock(HStoreFile.class);
182+
List<HStoreFile> list = new ArrayList<>();
183+
list.add(mockedFile);
184+
when(mockedFile.getPath()).thenReturn(filePath);
185+
when(mockedFileSystem.rename(any(),any())).thenReturn(true);
186+
archivingFunction.apply(conf, mockedFileSystem, mockedRegion, tableDir, family, list);
187+
ArgumentCaptor<Path> pathCaptor = ArgumentCaptor.forClass(Path.class);
188+
verify(mockedFileSystem, times(2)).rename(pathCaptor.capture(), any());
189+
String expectedDir = expectedBase +
190+
"archive/data/default/mockTable/mocked-region-encoded-name/testfamily/mockFile";
191+
assertTrue(pathCaptor.getAllValues().get(0).toString().equals(expectedDir));
192+
}
193+
194+
@FunctionalInterface
195+
private interface ArchivingFunction<Configuration, FS, Region, Dir, Family, Files> {
196+
void apply(Configuration config, FS fs, Region region, Dir dir, Family family, Files files)
197+
throws IOException;
198+
}
199+
200+
@Test
201+
public void testArchiveRecoveredEditsWalDirNull() throws Exception {
202+
testArchiveRecoveredEditsWalDirNullOrSame(null);
203+
}
204+
205+
@Test
206+
public void testArchiveRecoveredEditsWalDirSameFsStoreFiles() throws Exception {
207+
testArchiveRecoveredEditsWalDirNullOrSame("/wal-dir");
208+
}
209+
210+
private void testArchiveRecoveredEditsWalDirNullOrSame(String walDir) throws Exception {
211+
String originalRootDir = UTIL.getConfiguration().get(HConstants.HBASE_DIR);
212+
try {
213+
String baseDir = "mockFS://mockFSAuthority:9876/hbase/";
214+
UTIL.getConfiguration().set(HConstants.HBASE_DIR, baseDir);
215+
testArchiveStoreFilesDifferentFileSystems(walDir, baseDir,
216+
(conf, fs, region, dir, family, list) -> HFileArchiver
217+
.archiveRecoveredEdits(conf, fs, region, family, list));
218+
} finally {
219+
UTIL.getConfiguration().set(HConstants.HBASE_DIR, originalRootDir);
220+
}
221+
}
222+
223+
@Test(expected = IOException.class)
224+
public void testArchiveRecoveredEditsWrongFS() throws Exception {
225+
String baseDir = FSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/";
226+
//Internally, testArchiveStoreFilesDifferentFileSystems will pass a "mockedFS"
227+
// to HFileArchiver.archiveRecoveredEdits, but since wal-dir is supposedly on same FS
228+
// as root dir it would lead to conflicting FSes and an IOException is expected.
229+
testArchiveStoreFilesDifferentFileSystems("/wal-dir", baseDir,
230+
(conf, fs, region, dir, family, list) -> HFileArchiver
231+
.archiveRecoveredEdits(conf, fs, region, family, list));
232+
}
233+
234+
@Test
235+
public void testArchiveRecoveredEditsWalDirDifferentFS() throws Exception {
236+
String walDir = "mockFS://mockFSAuthority:9876/mockDir/wals/";
237+
testArchiveStoreFilesDifferentFileSystems(walDir, walDir,
238+
(conf, fs, region, dir, family, list) ->
239+
HFileArchiver.archiveRecoveredEdits(conf, fs, region, family, list));
240+
}
241+
131242
@Test
132243
public void testRemoveRegionDirOnArchive() throws Exception {
133244
final TableName tableName = TableName.valueOf(name.getMethodName());

0 commit comments

Comments
 (0)