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.snapshot; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.io.IOException; 024import java.util.List; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileSystem; 027import org.apache.hadoop.fs.LocatedFileStatus; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.fs.RemoteIterator; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseTestingUtility; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.client.RegionInfo; 035import org.apache.hadoop.hbase.client.Table; 036import org.apache.hadoop.hbase.client.TableDescriptor; 037import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher; 038import org.apache.hadoop.hbase.io.HFileLink; 039import org.apache.hadoop.hbase.mob.MobUtils; 040import org.apache.hadoop.hbase.monitoring.MonitoredTask; 041import org.apache.hadoop.hbase.regionserver.HRegion; 042import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 043import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils.SnapshotMock; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.RegionServerTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.CommonFSUtils; 048import org.apache.hadoop.hbase.util.FSTableDescriptors; 049import org.apache.hadoop.hbase.util.FSUtils; 050import org.apache.hadoop.hbase.wal.WALSplitUtil; 051import org.junit.After; 052import org.junit.AfterClass; 053import org.junit.Assert; 054import org.junit.Before; 055import org.junit.BeforeClass; 056import org.junit.ClassRule; 057import org.junit.Test; 058import org.junit.experimental.categories.Category; 059import org.mockito.Mockito; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; 064 065/** 066 * Test the restore/clone operation from a file-system point of view. 067 */ 068@Category({RegionServerTests.class, MediumTests.class}) 069public class TestRestoreSnapshotHelper { 070 071 @ClassRule 072 public static final HBaseClassTestRule CLASS_RULE = 073 HBaseClassTestRule.forClass(TestRestoreSnapshotHelper.class); 074 075 private static final Logger LOG = LoggerFactory.getLogger(TestRestoreSnapshotHelper.class); 076 077 protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 078 protected final static String TEST_HFILE = "abc"; 079 080 protected Configuration conf; 081 protected Path archiveDir; 082 protected FileSystem fs; 083 protected Path rootDir; 084 085 protected void setupConf(Configuration conf) { 086 } 087 088 @BeforeClass 089 public static void setupCluster() throws Exception { 090 TEST_UTIL.startMiniCluster(); 091 } 092 093 @AfterClass 094 public static void tearDownCluster() throws Exception { 095 TEST_UTIL.shutdownMiniCluster(); 096 } 097 098 @Before 099 public void setup() throws Exception { 100 rootDir = TEST_UTIL.getDataTestDir("testRestore"); 101 archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY); 102 fs = TEST_UTIL.getTestFileSystem(); 103 conf = TEST_UTIL.getConfiguration(); 104 setupConf(conf); 105 FSUtils.setRootDir(conf, rootDir); 106 } 107 108 @After 109 public void tearDown() throws Exception { 110 fs.delete(TEST_UTIL.getDataTestDir(), true); 111 } 112 113 protected SnapshotMock createSnapshotMock() throws IOException { 114 return new SnapshotMock(TEST_UTIL.getConfiguration(), fs, rootDir); 115 } 116 117 @Test 118 public void testRestore() throws IOException { 119 restoreAndVerify("snapshot", "testRestore"); 120 } 121 122 @Test 123 public void testRestoreWithNamespace() throws IOException { 124 restoreAndVerify("snapshot", "namespace1:testRestoreWithNamespace"); 125 } 126 127 @Test 128 public void testNoHFileLinkInRootDir() throws IOException { 129 rootDir = TEST_UTIL.getDefaultRootDirPath(); 130 FSUtils.setRootDir(conf, rootDir); 131 fs = rootDir.getFileSystem(conf); 132 133 TableName tableName = TableName.valueOf("testNoHFileLinkInRootDir"); 134 String snapshotName = tableName.getNameAsString() + "-snapshot"; 135 createTableAndSnapshot(tableName, snapshotName); 136 137 Path restoreDir = new Path("/hbase/.tmp-restore"); 138 RestoreSnapshotHelper.copySnapshotForScanner(conf, fs, rootDir, restoreDir, snapshotName); 139 checkNoHFileLinkInTableDir(tableName); 140 } 141 142 @Test 143 public void testSkipReplayAndUpdateSeqId() throws Exception { 144 rootDir = TEST_UTIL.getDefaultRootDirPath(); 145 FSUtils.setRootDir(conf, rootDir); 146 TableName tableName = TableName.valueOf("testSkipReplayAndUpdateSeqId"); 147 String snapshotName = "testSkipReplayAndUpdateSeqId"; 148 createTableAndSnapshot(tableName, snapshotName); 149 // put some data in the table 150 Table table = TEST_UTIL.getConnection().getTable(tableName); 151 TEST_UTIL.loadTable(table, Bytes.toBytes("A")); 152 153 Configuration conf = TEST_UTIL.getConfiguration(); 154 Path rootDir = FSUtils.getRootDir(conf); 155 Path restoreDir = new Path("/hbase/.tmp-restore/testScannerWithRestoreScanner2"); 156 // restore snapshot. 157 final RestoreSnapshotHelper.RestoreMetaChanges meta = 158 RestoreSnapshotHelper.copySnapshotForScanner(conf, fs, rootDir, restoreDir, snapshotName); 159 TableDescriptor htd = meta.getTableDescriptor(); 160 final List<RegionInfo> restoredRegions = meta.getRegionsToAdd(); 161 for (RegionInfo restoredRegion : restoredRegions) { 162 // open restored region 163 HRegion region = HRegion.newHRegion(FSUtils.getTableDir(restoreDir, tableName), null, fs, 164 conf, restoredRegion, htd, null); 165 // set restore flag 166 region.setRestoredRegion(true); 167 region.initialize(); 168 Path recoveredEdit = 169 FSUtils.getWALRegionDir(conf, tableName, region.getRegionInfo().getEncodedName()); 170 long maxSeqId = WALSplitUtil.getMaxRegionSequenceId(fs, recoveredEdit); 171 172 // open restored region without set restored flag 173 HRegion region2 = HRegion.newHRegion(FSUtils.getTableDir(restoreDir, tableName), null, fs, 174 conf, restoredRegion, htd, null); 175 region2.initialize(); 176 long maxSeqId2 = WALSplitUtil.getMaxRegionSequenceId(fs, recoveredEdit); 177 Assert.assertTrue(maxSeqId2 > maxSeqId); 178 } 179 } 180 181 protected void createTableAndSnapshot(TableName tableName, String snapshotName) 182 throws IOException { 183 byte[] column = Bytes.toBytes("A"); 184 Table table = TEST_UTIL.createTable(tableName, column, 2); 185 TEST_UTIL.loadTable(table, column); 186 TEST_UTIL.getAdmin().snapshot(snapshotName, tableName); 187 } 188 189 private void checkNoHFileLinkInTableDir(TableName tableName) throws IOException { 190 Path[] tableDirs = new Path[] { CommonFSUtils.getTableDir(rootDir, tableName), 191 CommonFSUtils.getTableDir(new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY), tableName), 192 CommonFSUtils.getTableDir(MobUtils.getMobHome(rootDir), tableName) }; 193 for (Path tableDir : tableDirs) { 194 Assert.assertFalse(hasHFileLink(tableDir)); 195 } 196 } 197 198 private boolean hasHFileLink(Path tableDir) throws IOException { 199 if (fs.exists(tableDir)) { 200 RemoteIterator<LocatedFileStatus> iterator = fs.listFiles(tableDir, true); 201 while (iterator.hasNext()) { 202 LocatedFileStatus fileStatus = iterator.next(); 203 if (fileStatus.isFile() && HFileLink.isHFileLink(fileStatus.getPath())) { 204 return true; 205 } 206 } 207 } 208 return false; 209 } 210 211 private void restoreAndVerify(final String snapshotName, final String tableName) throws IOException { 212 // Test Rolling-Upgrade like Snapshot. 213 // half machines writing using v1 and the others using v2 format. 214 SnapshotMock snapshotMock = createSnapshotMock(); 215 SnapshotMock.SnapshotBuilder builder = snapshotMock.createSnapshotV2("snapshot", tableName); 216 builder.addRegionV1(); 217 builder.addRegionV2(); 218 builder.addRegionV2(); 219 builder.addRegionV1(); 220 Path snapshotDir = builder.commit(); 221 TableDescriptor htd = builder.getTableDescriptor(); 222 SnapshotDescription desc = builder.getSnapshotDescription(); 223 224 // Test clone a snapshot 225 TableDescriptor htdClone = snapshotMock.createHtd("testtb-clone"); 226 testRestore(snapshotDir, desc, htdClone); 227 verifyRestore(rootDir, htd, htdClone); 228 229 // Test clone a clone ("link to link") 230 SnapshotDescription cloneDesc = SnapshotDescription.newBuilder() 231 .setName("cloneSnapshot") 232 .setTable("testtb-clone") 233 .build(); 234 Path cloneDir = FSUtils.getTableDir(rootDir, htdClone.getTableName()); 235 TableDescriptor htdClone2 = snapshotMock.createHtd("testtb-clone2"); 236 testRestore(cloneDir, cloneDesc, htdClone2); 237 verifyRestore(rootDir, htd, htdClone2); 238 } 239 240 private void verifyRestore(final Path rootDir, final TableDescriptor sourceHtd, 241 final TableDescriptor htdClone) throws IOException { 242 List<String> files = SnapshotTestingUtils.listHFileNames(fs, 243 FSUtils.getTableDir(rootDir, htdClone.getTableName())); 244 assertEquals(12, files.size()); 245 for (int i = 0; i < files.size(); i += 2) { 246 String linkFile = files.get(i); 247 String refFile = files.get(i+1); 248 assertTrue(linkFile + " should be a HFileLink", HFileLink.isHFileLink(linkFile)); 249 assertTrue(refFile + " should be a Referene", StoreFileInfo.isReference(refFile)); 250 assertEquals(sourceHtd.getTableName(), HFileLink.getReferencedTableName(linkFile)); 251 Path refPath = getReferredToFile(refFile); 252 LOG.debug("get reference name for file " + refFile + " = " + refPath); 253 assertTrue(refPath.getName() + " should be a HFileLink", HFileLink.isHFileLink(refPath.getName())); 254 assertEquals(linkFile, refPath.getName()); 255 } 256 } 257 258 /** 259 * Execute the restore operation 260 * @param snapshotDir The snapshot directory to use as "restore source" 261 * @param sd The snapshot descriptor 262 * @param htdClone The HTableDescriptor of the table to restore/clone. 263 */ 264 private void testRestore(final Path snapshotDir, final SnapshotDescription sd, 265 final TableDescriptor htdClone) throws IOException { 266 LOG.debug("pre-restore table=" + htdClone.getTableName() + " snapshot=" + snapshotDir); 267 FSUtils.logFileSystemState(fs, rootDir, LOG); 268 269 new FSTableDescriptors(conf).createTableDescriptor(htdClone); 270 RestoreSnapshotHelper helper = getRestoreHelper(rootDir, snapshotDir, sd, htdClone); 271 helper.restoreHdfsRegions(); 272 273 LOG.debug("post-restore table=" + htdClone.getTableName() + " snapshot=" + snapshotDir); 274 FSUtils.logFileSystemState(fs, rootDir, LOG); 275 } 276 277 /** 278 * Initialize the restore helper, based on the snapshot and table information provided. 279 */ 280 private RestoreSnapshotHelper getRestoreHelper(final Path rootDir, final Path snapshotDir, 281 final SnapshotDescription sd, final TableDescriptor htdClone) throws IOException { 282 ForeignExceptionDispatcher monitor = Mockito.mock(ForeignExceptionDispatcher.class); 283 MonitoredTask status = Mockito.mock(MonitoredTask.class); 284 285 SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, sd); 286 return new RestoreSnapshotHelper(conf, fs, manifest, 287 htdClone, rootDir, monitor, status); 288 } 289 290 private Path getReferredToFile(final String referenceName) { 291 Path fakeBasePath = new Path(new Path("table", "region"), "cf"); 292 return StoreFileInfo.getReferredToFile(new Path(fakeBasePath, referenceName)); 293 } 294}