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.io.hfile.bucket; 019 020import static org.apache.hadoop.hbase.io.hfile.bucket.TestByteBufferIOEngine.createBucketEntry; 021import static org.apache.hadoop.hbase.io.hfile.bucket.TestByteBufferIOEngine.getByteBuff; 022import static org.junit.Assert.assertArrayEquals; 023import static org.junit.Assert.assertEquals; 024import static org.junit.Assert.assertNotEquals; 025import static org.junit.Assert.assertNotNull; 026import static org.junit.Assert.fail; 027 028import java.io.File; 029import java.io.IOException; 030import java.nio.ByteBuffer; 031import java.nio.channels.FileChannel; 032import java.util.ArrayList; 033import java.util.List; 034 035import org.apache.hadoop.hbase.HBaseClassTestRule; 036import org.apache.hadoop.hbase.io.ByteBuffAllocator; 037import org.apache.hadoop.hbase.nio.ByteBuff; 038import org.apache.hadoop.hbase.nio.RefCnt; 039import org.apache.hadoop.hbase.testclassification.IOTests; 040import org.apache.hadoop.hbase.testclassification.SmallTests; 041import org.junit.After; 042import org.junit.Assert; 043import org.junit.Before; 044import org.junit.ClassRule; 045import org.junit.Test; 046import org.junit.experimental.categories.Category; 047import org.mockito.Mockito; 048import org.mockito.invocation.InvocationOnMock; 049import org.mockito.stubbing.Answer; 050 051/** 052 * Basic test for {@link FileIOEngine} 053 */ 054@Category({IOTests.class, SmallTests.class}) 055public class TestFileIOEngine { 056 057 @ClassRule 058 public static final HBaseClassTestRule CLASS_RULE = 059 HBaseClassTestRule.forClass(TestFileIOEngine.class); 060 061 private static final long TOTAL_CAPACITY = 6 * 1024 * 1024; // 6 MB 062 private static final String[] FILE_PATHS = {"testFileIOEngine1", "testFileIOEngine2", 063 "testFileIOEngine3"}; 064 private static final long SIZE_PER_FILE = TOTAL_CAPACITY / FILE_PATHS.length; // 2 MB per File 065 private final static List<Long> boundaryStartPositions = new ArrayList<Long>(); 066 private final static List<Long> boundaryStopPositions = new ArrayList<Long>(); 067 068 private FileIOEngine fileIOEngine; 069 070 static { 071 boundaryStartPositions.add(0L); 072 for (int i = 1; i < FILE_PATHS.length; i++) { 073 boundaryStartPositions.add(SIZE_PER_FILE * i - 1); 074 boundaryStartPositions.add(SIZE_PER_FILE * i); 075 boundaryStartPositions.add(SIZE_PER_FILE * i + 1); 076 } 077 for (int i = 1; i < FILE_PATHS.length; i++) { 078 boundaryStopPositions.add(SIZE_PER_FILE * i - 1); 079 boundaryStopPositions.add(SIZE_PER_FILE * i); 080 boundaryStopPositions.add(SIZE_PER_FILE * i + 1); 081 } 082 boundaryStopPositions.add(SIZE_PER_FILE * FILE_PATHS.length - 1); 083 } 084 085 @Before 086 public void setUp() throws IOException { 087 fileIOEngine = new FileIOEngine(TOTAL_CAPACITY, false, FILE_PATHS); 088 } 089 090 @After 091 public void cleanUp() { 092 fileIOEngine.shutdown(); 093 for (String filePath : FILE_PATHS) { 094 File file = new File(filePath); 095 if (file.exists()) { 096 file.delete(); 097 } 098 } 099 } 100 101 @Test 102 public void testFileIOEngine() throws IOException { 103 for (int i = 0; i < 500; i++) { 104 int len = (int) Math.floor(Math.random() * 100) + 1; 105 long offset = (long) Math.floor(Math.random() * TOTAL_CAPACITY % (TOTAL_CAPACITY - len)); 106 if (i < boundaryStartPositions.size()) { 107 // make the boundary start positon 108 offset = boundaryStartPositions.get(i); 109 } else if ((i - boundaryStartPositions.size()) < boundaryStopPositions.size()) { 110 // make the boundary stop positon 111 offset = boundaryStopPositions.get(i - boundaryStartPositions.size()) - len + 1; 112 } else if (i % 2 == 0) { 113 // make the cross-files block writing/reading 114 offset = Math.max(1, i % FILE_PATHS.length) * SIZE_PER_FILE - len / 2; 115 } 116 byte[] data1 = new byte[len]; 117 for (int j = 0; j < data1.length; ++j) { 118 data1[j] = (byte) (Math.random() * 255); 119 } 120 fileIOEngine.write(ByteBuffer.wrap(data1), offset); 121 122 BucketEntry be = createBucketEntry(offset, len); 123 fileIOEngine.read(be); 124 ByteBuff data2 = getByteBuff(be); 125 assertArrayEquals(data1, data2.array()); 126 } 127 } 128 129 @Test 130 public void testFileIOEngineHandlesZeroLengthInput() throws IOException { 131 byte[] data1 = new byte[0]; 132 133 fileIOEngine.write(ByteBuffer.wrap(data1), 0); 134 BucketEntry be = createBucketEntry(0, 0); 135 fileIOEngine.read(be); 136 ByteBuff data2 = getByteBuff(be); 137 assertArrayEquals(data1, data2.array()); 138 } 139 140 @Test 141 public void testReadFailedShouldReleaseByteBuff() { 142 ByteBuffAllocator alloc = Mockito.mock(ByteBuffAllocator.class); 143 final RefCnt refCnt = RefCnt.create(); 144 Mockito.when(alloc.allocate(Mockito.anyInt())).thenAnswer(new Answer<ByteBuff>() { 145 @Override 146 public ByteBuff answer(InvocationOnMock invocation) throws Throwable { 147 int len = invocation.getArgument(0); 148 return ByteBuff.wrap(new ByteBuffer[]{ByteBuffer.allocate(len + 1)}, refCnt); 149 } 150 }); 151 int len = 10; 152 byte[] data1 = new byte[len]; 153 assertEquals(1, refCnt.refCnt()); 154 try { 155 fileIOEngine.write(ByteBuffer.wrap(data1), 0); 156 BucketEntry be = createBucketEntry(0, len, alloc); 157 fileIOEngine.read(be); 158 fail(); 159 } catch (IOException ioe) { 160 // expected exception. 161 } 162 assertEquals(0, refCnt.refCnt()); 163 } 164 165 @Test 166 public void testClosedChannelException() throws IOException { 167 fileIOEngine.closeFileChannels(); 168 int len = 5; 169 long offset = 0L; 170 int val = (int) (Math.random() * 255); 171 for (int i = 0; i < 2; i++) { 172 ByteBuff src = TestByteBufferIOEngine.createByteBuffer(len, val, i % 2 == 0); 173 int pos = src.position(), lim = src.limit(); 174 fileIOEngine.write(src, offset); 175 src.position(pos).limit(lim); 176 177 BucketEntry be = createBucketEntry(offset, len); 178 fileIOEngine.read(be); 179 ByteBuff dst = getByteBuff(be); 180 181 Assert.assertEquals(src.remaining(), len); 182 Assert.assertEquals(dst.remaining(), len); 183 Assert.assertEquals(0, 184 ByteBuff.compareTo(src, pos, len, dst, dst.position(), dst.remaining())); 185 } 186 } 187 188 @Test 189 public void testRefreshFileConnection() throws IOException { 190 FileChannel[] fileChannels = fileIOEngine.getFileChannels(); 191 FileChannel fileChannel = fileChannels[0]; 192 assertNotNull(fileChannel); 193 fileChannel.close(); 194 fileIOEngine.refreshFileConnection(0, new IOException("Test Exception")); 195 FileChannel[] reopenedFileChannels = fileIOEngine.getFileChannels(); 196 FileChannel reopenedFileChannel = reopenedFileChannels[0]; 197 assertNotEquals(fileChannel, reopenedFileChannel); 198 assertEquals(fileChannels.length, reopenedFileChannels.length); 199 for (int i = 1; i < fileChannels.length; i++) { 200 assertEquals(fileChannels[i], reopenedFileChannels[i]); 201 } 202 } 203}