| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hdfs.server.blockmanagement; |
| |
| import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.BLOCK_GROUP_INDEX_MASK; |
| import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.MAX_BLOCKS_IN_GROUP; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.junit.Assert.assertThat; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.spy; |
| |
| import java.io.IOException; |
| import java.util.List; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.hdfs.DFSConfigKeys; |
| import org.apache.hadoop.hdfs.DFSTestUtil; |
| import org.apache.hadoop.hdfs.HdfsConfiguration; |
| import org.apache.hadoop.hdfs.MiniDFSCluster; |
| import org.apache.hadoop.hdfs.protocol.HdfsConstants; |
| import org.apache.hadoop.hdfs.protocol.LocatedBlock; |
| import org.apache.hadoop.hdfs.server.namenode.FSNamesystem; |
| import org.apache.hadoop.test.GenericTestUtils; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.internal.util.reflection.Whitebox; |
| import org.mockito.stubbing.Answer; |
| |
| /** |
| * Tests the sequential blockGroup ID generation mechanism and blockGroup ID |
| * collision handling. |
| */ |
| public class TestSequentialBlockGroupId { |
| private static final Log LOG = LogFactory |
| .getLog("TestSequentialBlockGroupId"); |
| |
| private final short REPLICATION = 1; |
| private final long SEED = 0; |
| private final int dataBlocks = HdfsConstants.NUM_DATA_BLOCKS; |
| private final int parityBlocks = HdfsConstants.NUM_PARITY_BLOCKS; |
| private final int cellSize = HdfsConstants.BLOCK_STRIPED_CELL_SIZE; |
| |
| private final int stripesPerBlock = 2; |
| private final int blockSize = cellSize * stripesPerBlock; |
| private final int numDNs = dataBlocks + parityBlocks + 2; |
| private final int blockGrpCount = 4; |
| private final int fileLen = blockSize * dataBlocks * blockGrpCount; |
| |
| private MiniDFSCluster cluster; |
| private FileSystem fs; |
| private SequentialBlockGroupIdGenerator blockGrpIdGenerator; |
| private Path eczone = new Path("/eczone"); |
| |
| @Before |
| public void setup() throws Exception { |
| Configuration conf = new HdfsConfiguration(); |
| conf.setInt(DFSConfigKeys.DFS_REPLICATION_KEY, 1); |
| conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, blockSize); |
| cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDNs).build(); |
| cluster.waitActive(); |
| |
| fs = cluster.getFileSystem(); |
| blockGrpIdGenerator = cluster.getNamesystem().getBlockIdManager() |
| .getBlockGroupIdGenerator(); |
| fs.mkdirs(eczone); |
| cluster.getFileSystem().getClient() |
| .createErasureCodingZone("/eczone", null); |
| } |
| |
| @After |
| public void teardown() { |
| if (cluster != null) { |
| cluster.shutdown(); |
| } |
| } |
| |
| /** |
| * Test that blockGroup IDs are generating unique value. |
| */ |
| @Test(timeout = 60000) |
| public void testBlockGroupIdGeneration() throws IOException { |
| long blockGroupIdInitialValue = blockGrpIdGenerator.getCurrentValue(); |
| |
| // Create a file that is 4 blocks long. |
| Path path = new Path(eczone, "testBlockGrpIdGeneration.dat"); |
| DFSTestUtil.createFile(fs, path, cellSize, fileLen, blockSize, REPLICATION, |
| SEED); |
| List<LocatedBlock> blocks = DFSTestUtil.getAllBlocks(fs, path); |
| assertThat("Wrong BlockGrps", blocks.size(), is(blockGrpCount)); |
| |
| // initialising the block group generator for verifying the block id |
| blockGrpIdGenerator.setCurrentValue(blockGroupIdInitialValue); |
| // Ensure that the block IDs are generating unique value. |
| for (int i = 0; i < blocks.size(); ++i) { |
| blockGrpIdGenerator |
| .skipTo((blockGrpIdGenerator.getCurrentValue() & ~BLOCK_GROUP_INDEX_MASK) |
| + MAX_BLOCKS_IN_GROUP); |
| long nextBlockExpectedId = blockGrpIdGenerator.getCurrentValue(); |
| long nextBlockGrpId = blocks.get(i).getBlock().getBlockId(); |
| LOG.info("BlockGrp" + i + " id is " + nextBlockGrpId); |
| assertThat("BlockGrpId mismatches!", nextBlockGrpId, |
| is(nextBlockExpectedId)); |
| } |
| } |
| |
| /** |
| * Test that collisions in the blockGroup ID space are handled gracefully. |
| */ |
| @Test(timeout = 60000) |
| public void testTriggerBlockGroupIdCollision() throws IOException { |
| long blockGroupIdInitialValue = blockGrpIdGenerator.getCurrentValue(); |
| |
| // Create a file with a few blocks to rev up the global block ID |
| // counter. |
| Path path1 = new Path(eczone, "testBlockGrpIdCollisionDetection_file1.dat"); |
| DFSTestUtil.createFile(fs, path1, cellSize, fileLen, blockSize, |
| REPLICATION, SEED); |
| List<LocatedBlock> blocks1 = DFSTestUtil.getAllBlocks(fs, path1); |
| assertThat("Wrong BlockGrps", blocks1.size(), is(blockGrpCount)); |
| |
| // Rewind the block ID counter in the name system object. This will result |
| // in block ID collisions when we try to allocate new blocks. |
| blockGrpIdGenerator.setCurrentValue(blockGroupIdInitialValue); |
| |
| // Trigger collisions by creating a new file. |
| Path path2 = new Path(eczone, "testBlockGrpIdCollisionDetection_file2.dat"); |
| DFSTestUtil.createFile(fs, path2, cellSize, fileLen, blockSize, |
| REPLICATION, SEED); |
| List<LocatedBlock> blocks2 = DFSTestUtil.getAllBlocks(fs, path2); |
| assertThat("Wrong BlockGrps", blocks2.size(), is(blockGrpCount)); |
| |
| // Make sure that file1 and file2 block IDs are different |
| for (LocatedBlock locBlock1 : blocks1) { |
| long blockId1 = locBlock1.getBlock().getBlockId(); |
| for (LocatedBlock locBlock2 : blocks2) { |
| long blockId2 = locBlock2.getBlock().getBlockId(); |
| assertThat("BlockGrpId mismatches!", blockId1, is(not(blockId2))); |
| } |
| } |
| } |
| |
| /** |
| * Test that collisions in the blockGroup ID when the id is occupied by legacy |
| * block. |
| */ |
| @Test(timeout = 60000) |
| public void testTriggerBlockGroupIdCollisionWithLegacyBlockId() |
| throws Exception { |
| long blockGroupIdInitialValue = blockGrpIdGenerator.getCurrentValue(); |
| blockGrpIdGenerator |
| .skipTo((blockGrpIdGenerator.getCurrentValue() & ~BLOCK_GROUP_INDEX_MASK) |
| + MAX_BLOCKS_IN_GROUP); |
| final long curBlockGroupIdValue = blockGrpIdGenerator.getCurrentValue(); |
| |
| // Creates contiguous block with negative blockId so that it would trigger |
| // collision during blockGroup Id generation |
| FSNamesystem fsn = cluster.getNamesystem(); |
| // Replace SequentialBlockIdGenerator with a spy |
| SequentialBlockIdGenerator blockIdGenerator = spy(fsn.getBlockIdManager() |
| .getBlockIdGenerator()); |
| Whitebox.setInternalState(fsn.getBlockIdManager(), "blockIdGenerator", |
| blockIdGenerator); |
| SequentialBlockIdGenerator spySequentialBlockIdGenerator = new SequentialBlockIdGenerator( |
| null) { |
| @Override |
| public long nextValue() { |
| return curBlockGroupIdValue; |
| } |
| }; |
| final Answer<Object> delegator = new GenericTestUtils.DelegateAnswer( |
| spySequentialBlockIdGenerator); |
| doAnswer(delegator).when(blockIdGenerator).nextValue(); |
| |
| Path path1 = new Path("/testCollisionWithLegacyBlock_file1.dat"); |
| DFSTestUtil.createFile(fs, path1, 1024, REPLICATION, SEED); |
| |
| List<LocatedBlock> contiguousBlocks = DFSTestUtil.getAllBlocks(fs, path1); |
| assertThat(contiguousBlocks.size(), is(1)); |
| Assert.assertEquals("Unexpected BlockId!", curBlockGroupIdValue, |
| contiguousBlocks.get(0).getBlock().getBlockId()); |
| |
| // Reset back to the initial value to trigger collision |
| blockGrpIdGenerator.setCurrentValue(blockGroupIdInitialValue); |
| // Trigger collisions by creating a new file. |
| Path path2 = new Path(eczone, "testCollisionWithLegacyBlock_file2.dat"); |
| DFSTestUtil.createFile(fs, path2, cellSize, fileLen, blockSize, |
| REPLICATION, SEED); |
| List<LocatedBlock> blocks2 = DFSTestUtil.getAllBlocks(fs, path2); |
| assertThat("Wrong BlockGrps", blocks2.size(), is(blockGrpCount)); |
| |
| // Make sure that file1 and file2 block IDs are different |
| for (LocatedBlock locBlock1 : contiguousBlocks) { |
| long blockId1 = locBlock1.getBlock().getBlockId(); |
| for (LocatedBlock locBlock2 : blocks2) { |
| long blockId2 = locBlock2.getBlock().getBlockId(); |
| assertThat("BlockGrpId mismatches!", blockId1, is(not(blockId2))); |
| } |
| } |
| } |
| } |