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.wal; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNotNull; 022import static org.mockito.Mockito.mock; 023import static org.mockito.Mockito.when; 024 025import java.io.IOException; 026import java.lang.reflect.Field; 027import java.util.List; 028import java.util.NavigableMap; 029import java.util.TreeMap; 030import java.util.concurrent.CountDownLatch; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.Executors; 033import java.util.concurrent.atomic.AtomicBoolean; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.fs.FSDataOutputStream; 036import org.apache.hadoop.fs.FileSystem; 037import org.apache.hadoop.fs.Path; 038import org.apache.hadoop.hbase.HConstants; 039import org.apache.hadoop.hbase.TableName; 040import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 041import org.apache.hadoop.hbase.client.Put; 042import org.apache.hadoop.hbase.client.RegionInfo; 043import org.apache.hadoop.hbase.client.RegionInfoBuilder; 044import org.apache.hadoop.hbase.client.TableDescriptor; 045import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 046import org.apache.hadoop.hbase.regionserver.ChunkCreator; 047import org.apache.hadoop.hbase.regionserver.HRegion; 048import org.apache.hadoop.hbase.regionserver.MemStoreLAB; 049import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl; 050import org.apache.hadoop.hbase.testclassification.MediumTests; 051import org.apache.hadoop.hbase.testclassification.RegionServerTests; 052import org.apache.hadoop.hbase.util.Bytes; 053import org.apache.hadoop.hbase.util.CommonFSUtils; 054import org.apache.hadoop.hbase.util.Threads; 055import org.apache.hadoop.hbase.wal.WALEdit; 056import org.apache.hadoop.hbase.wal.WALKey; 057import org.apache.hadoop.hdfs.DFSOutputStream; 058import org.apache.hadoop.hdfs.client.HdfsDataOutputStream; 059import org.apache.hadoop.hdfs.protocol.DatanodeInfo; 060import org.junit.jupiter.api.BeforeEach; 061import org.junit.jupiter.api.Tag; 062import org.junit.jupiter.api.Test; 063import org.junit.jupiter.api.TestInfo; 064 065/** 066 * Provides FSHLog test cases. 067 */ 068@Tag(RegionServerTests.TAG) 069@Tag(MediumTests.TAG) 070public class TestFSHLog extends AbstractTestFSWAL { 071 072 private String name; 073 074 @BeforeEach 075 public void initTestName(TestInfo testInfo) { 076 name = testInfo.getTestMethod().get().getName(); 077 } 078 079 @Override 080 protected AbstractFSWAL<?> newWAL(FileSystem fs, Path rootDir, String walDir, String archiveDir, 081 Configuration conf, List<WALActionsListener> listeners, boolean failIfWALExists, String prefix, 082 String suffix) throws IOException { 083 FSHLog wal = 084 new FSHLog(fs, rootDir, walDir, archiveDir, conf, listeners, failIfWALExists, prefix, suffix); 085 wal.init(); 086 return wal; 087 } 088 089 @Override 090 protected AbstractFSWAL<?> newSlowWAL(FileSystem fs, Path rootDir, String walDir, 091 String archiveDir, Configuration conf, List<WALActionsListener> listeners, 092 boolean failIfWALExists, String prefix, String suffix, final Runnable action) 093 throws IOException { 094 FSHLog wal = new FSHLog(fs, rootDir, walDir, archiveDir, conf, listeners, failIfWALExists, 095 prefix, suffix) { 096 097 @Override 098 protected void atHeadOfRingBufferEventHandlerAppend() { 099 action.run(); 100 super.atHeadOfRingBufferEventHandlerAppend(); 101 } 102 }; 103 wal.init(); 104 return wal; 105 } 106 107 @Test 108 public void testSyncRunnerIndexOverflow() throws IOException, NoSuchFieldException, 109 SecurityException, IllegalArgumentException, IllegalAccessException { 110 FS.mkdirs(new Path(CommonFSUtils.getRootDir(CONF), this.name)); 111 FSHLog log = new FSHLog(FS, CommonFSUtils.getRootDir(CONF), this.name, 112 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null); 113 log.init(); 114 try { 115 Field syncRunnerIndexField = FSHLog.class.getDeclaredField("syncRunnerIndex"); 116 syncRunnerIndexField.setAccessible(true); 117 syncRunnerIndexField.set(log, Integer.MAX_VALUE - 1); 118 TableDescriptor htd = TableDescriptorBuilder.newBuilder(TableName.valueOf(this.name)) 119 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("row")).build(); 120 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 121 for (byte[] fam : htd.getColumnFamilyNames()) { 122 scopes.put(fam, 0); 123 } 124 RegionInfo hri = RegionInfoBuilder.newBuilder(htd.getTableName()).build(); 125 MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(); 126 for (int i = 0; i < 10; i++) { 127 addEdits(log, hri, htd, 1, mvcc, scopes, "row"); 128 } 129 } finally { 130 log.close(); 131 } 132 } 133 134 /** 135 * Test case for https://issues.apache.org/jira/browse/HBASE-16721 136 */ 137 @Test 138 public void testUnflushedSeqIdTracking() throws IOException, InterruptedException { 139 final byte[] b = Bytes.toBytes("b"); 140 141 final AtomicBoolean startHoldingForAppend = new AtomicBoolean(false); 142 final CountDownLatch holdAppend = new CountDownLatch(1); 143 final CountDownLatch flushFinished = new CountDownLatch(1); 144 final CountDownLatch putFinished = new CountDownLatch(1); 145 146 FS.mkdirs(new Path(CommonFSUtils.getRootDir(CONF), this.name)); 147 try (FSHLog log = new FSHLog(FS, CommonFSUtils.getRootDir(CONF), this.name, 148 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null)) { 149 log.init(); 150 log.registerWALActionsListener(new WALActionsListener() { 151 @Override 152 public void visitLogEntryBeforeWrite(RegionInfo info, WALKey logKey, WALEdit logEdit) { 153 if (startHoldingForAppend.get()) { 154 try { 155 holdAppend.await(); 156 } catch (InterruptedException e) { 157 LOG.error(e.toString(), e); 158 } 159 } 160 } 161 }); 162 163 // open a new region which uses this WAL 164 TableDescriptor htd = TableDescriptorBuilder.newBuilder(TableName.valueOf(this.name)) 165 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(b)).build(); 166 RegionInfo hri = RegionInfoBuilder.newBuilder(htd.getTableName()).build(); 167 ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null, 168 MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT); 169 final HRegion region = TEST_UTIL.createLocalHRegion(hri, CONF, htd, log); 170 ExecutorService exec = Executors.newFixedThreadPool(2); 171 172 // do a regular write first because of memstore size calculation. 173 region.put(new Put(b).addColumn(b, b, b)); 174 175 startHoldingForAppend.set(true); 176 exec.submit(new Runnable() { 177 @Override 178 public void run() { 179 try { 180 region.put(new Put(b).addColumn(b, b, b)); 181 putFinished.countDown(); 182 } catch (IOException e) { 183 LOG.error(e.toString(), e); 184 } 185 } 186 }); 187 188 // give the put a chance to start 189 Threads.sleep(3000); 190 191 exec.submit(new Runnable() { 192 @Override 193 public void run() { 194 try { 195 HRegion.FlushResult flushResult = region.flush(true); 196 LOG.info("Flush result:" + flushResult.getResult()); 197 LOG.info("Flush succeeded:" + flushResult.isFlushSucceeded()); 198 flushFinished.countDown(); 199 } catch (IOException e) { 200 LOG.error(e.toString(), e); 201 } 202 } 203 }); 204 205 // give the flush a chance to start. Flush should have got the region lock, and 206 // should have been waiting on the mvcc complete after this. 207 Threads.sleep(3000); 208 209 // let the append to WAL go through now that the flush already started 210 holdAppend.countDown(); 211 putFinished.await(); 212 flushFinished.await(); 213 214 // check whether flush went through 215 assertEquals(1, region.getStoreFileList(new byte[][] { b }).size(), "Region did not flush?"); 216 217 // now check the region's unflushed seqIds. 218 long seqId = AbstractTestFSWAL.getEarliestMemStoreSeqNum(log, hri.getEncodedNameAsBytes()); 219 assertEquals(HConstants.NO_SEQNUM, seqId, 220 "Found seqId for the region which is already flushed"); 221 222 region.close(); 223 } 224 } 225 226 /** 227 * Regression test for HBASE-30346: FSHLog#getPipeline() must never return null, even when the 228 * underlying DFSOutputStream#getPipeline() legitimately returns null (e.g. the DFS streamer is 229 * closed, or no block pipeline is currently established -- see HDFS-826 and the 230 * DFSOutputStream#getPipeline() javadoc: "returns the list of targets, if any"). Prior to this 231 * fix, AbstractFSWAL#rollWriterInternal's debug-log statement called 232 * Arrays.stream(getPipeline()), which threw a NullPointerException whenever this happened during 233 * a WAL roll, aborting the RegionServer on what was otherwise a successful roll. 234 */ 235 @Test 236 public void testGetPipelineDoesNotReturnNullWhenUnderlyingStreamerHasNone() throws Exception { 237 FS.mkdirs(new Path(CommonFSUtils.getRootDir(CONF), this.name)); 238 try (FSHLog log = new FSHLog(FS, CommonFSUtils.getRootDir(CONF), this.name, 239 HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null)) { 240 log.init(); 241 242 // Simulate the legitimate HDFS contract: the wrapped DFSOutputStream currently has no 243 // established pipeline (e.g. streamer closed, or between blocks) and returns null. 244 DFSOutputStream mockDfsOut = mock(DFSOutputStream.class); 245 when(mockDfsOut.getPipeline()).thenReturn(null); 246 FSDataOutputStream wrappedOut = new HdfsDataOutputStream(mockDfsOut, null); 247 248 Field hdfsOutField = FSHLog.class.getDeclaredField("hdfs_out"); 249 hdfsOutField.setAccessible(true); 250 hdfsOutField.set(log, wrappedOut); 251 252 DatanodeInfo[] pipeline = log.getPipeline(); 253 assertNotNull(pipeline, "getPipeline() must never return null"); 254 assertEquals(0, pipeline.length, 255 "Should normalize a null underlying pipeline to an empty array"); 256 } 257 } 258}