/** * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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.datanode.checker; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.server.datanode.fsdataset.*; import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeSpi.VolumeCheckContext; import org.apache.hadoop.util.FakeTimer; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.channels.ClosedChannelException; import java.util.concurrent.TimeUnit; import java.util.*; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; /** * Test a few more conditions not covered by TestDatasetVolumeChecker. */ public class TestDatasetVolumeCheckerFailures { public static final Logger LOG =LoggerFactory.getLogger( TestDatasetVolumeCheckerFailures.class); private FakeTimer timer; private Configuration conf; private static final long MIN_DISK_CHECK_GAP_MS = 1000; // 1 second. @Before public void commonInit() { timer = new FakeTimer(); conf = new HdfsConfiguration(); conf.setTimeDuration(DFSConfigKeys.DFS_DATANODE_DISK_CHECK_MIN_GAP_KEY, MIN_DISK_CHECK_GAP_MS, TimeUnit.MILLISECONDS); } /** * Test timeout in {@link DatasetVolumeChecker#checkAllVolumes}. * @throws Exception */ @Test(timeout=60000) public void testTimeout() throws Exception { // Add a volume whose check routine hangs forever. final List<FsVolumeSpi> volumes = Collections.singletonList(makeHungVolume()); final FsDatasetSpi<FsVolumeSpi> dataset = TestDatasetVolumeChecker.makeDataset(volumes); // Create a disk checker with a very low timeout. conf.setTimeDuration(DFSConfigKeys.DFS_DATANODE_DISK_CHECK_TIMEOUT_KEY, 1, TimeUnit.SECONDS); final DatasetVolumeChecker checker = new DatasetVolumeChecker(conf, new FakeTimer()); // Ensure that the hung volume is detected as failed. Set<FsVolumeSpi> failedVolumes = checker.checkAllVolumes(dataset); assertThat(failedVolumes.size(), is(1)); } /** * Test checking a closed volume i.e. one which cannot be referenced. * * @throws Exception */ @Test(timeout=60000) public void testCheckingClosedVolume() throws Exception { // Add a volume that cannot be referenced. final List<FsVolumeSpi> volumes = Collections.singletonList(makeClosedVolume()); final FsDatasetSpi<FsVolumeSpi> dataset = TestDatasetVolumeChecker.makeDataset(volumes); DatasetVolumeChecker checker = new DatasetVolumeChecker(conf, timer); Set<FsVolumeSpi> failedVolumes = checker.checkAllVolumes(dataset); assertThat(failedVolumes.size(), is(0)); assertThat(checker.getNumSyncDatasetChecks(), is(0L)); // The closed volume should not have been checked as it cannot // be referenced. verify(volumes.get(0), times(0)).check(any(VolumeCheckContext.class)); } @Test(timeout=60000) public void testMinGapIsEnforcedForSyncChecks() throws Exception { final List<FsVolumeSpi> volumes = TestDatasetVolumeChecker.makeVolumes(1, VolumeCheckResult.HEALTHY); final FsDatasetSpi<FsVolumeSpi> dataset = TestDatasetVolumeChecker.makeDataset(volumes); final DatasetVolumeChecker checker = new DatasetVolumeChecker(conf, timer); checker.checkAllVolumes(dataset); assertThat(checker.getNumSyncDatasetChecks(), is(1L)); // Re-check without advancing the timer. Ensure the check is skipped. checker.checkAllVolumes(dataset); assertThat(checker.getNumSyncDatasetChecks(), is(1L)); assertThat(checker.getNumSkippedChecks(), is(1L)); // Re-check after advancing the timer. Ensure the check is performed. timer.advance(MIN_DISK_CHECK_GAP_MS); checker.checkAllVolumes(dataset); assertThat(checker.getNumSyncDatasetChecks(), is(2L)); assertThat(checker.getNumSkippedChecks(), is(1L)); } /** * Create a mock FsVolumeSpi whose {@link FsVolumeSpi#check} routine * hangs forever. * * @return volume * @throws Exception */ private static FsVolumeSpi makeHungVolume() throws Exception { final FsVolumeSpi volume = mock(FsVolumeSpi.class); final FsVolumeReference reference = mock(FsVolumeReference.class); when(reference.getVolume()).thenReturn(volume); when(volume.obtainReference()).thenReturn(reference); when(volume.check(any(VolumeCheckContext.class))).thenAnswer( new Answer<VolumeCheckResult>() { @Override public VolumeCheckResult answer(InvocationOnMock invocation) throws Throwable { Thread.sleep(Long.MAX_VALUE); // Sleep forever. return VolumeCheckResult.HEALTHY; // unreachable. } }); return volume; } /** * Create a mock FsVolumeSpi which is closed and hence cannot * be referenced. * * @return volume * @throws Exception */ private static FsVolumeSpi makeClosedVolume() throws Exception { final FsVolumeSpi volume = mock(FsVolumeSpi.class); when(volume.obtainReference()).thenThrow(new ClosedChannelException()); return volume; } }