HDDS-267. Handle consistency issues during container update/close.

This commit is contained in:
Hanisha Koneru 2018-08-08 16:47:25 -07:00
parent 8478732bb2
commit d81cd3611a
6 changed files with 80 additions and 75 deletions

View File

@ -257,7 +257,6 @@ public abstract class ContainerData {
* Marks this container as closed. * Marks this container as closed.
*/ */
public synchronized void closeContainer() { public synchronized void closeContainer() {
// TODO: closed or closing here
setState(ContainerLifeCycleState.CLOSED); setState(ContainerLifeCycleState.CLOSED);
} }

View File

@ -138,7 +138,7 @@ public class KeyValueContainer implements Container {
// Create .container file // Create .container file
File containerFile = getContainerFile(); File containerFile = getContainerFile();
writeToContainerFile(containerFile, true); createContainerFile(containerFile);
} catch (StorageContainerException ex) { } catch (StorageContainerException ex) {
if (containerMetaDataPath != null && containerMetaDataPath.getParentFile() if (containerMetaDataPath != null && containerMetaDataPath.getParentFile()
@ -165,11 +165,11 @@ public class KeyValueContainer implements Container {
} }
/** /**
* Creates .container file and checksum file. * Writes to .container file.
* *
* @param containerFile * @param containerFile container file name
* @param isCreate true if we are creating a new container file and false if * @param isCreate True if creating a new file. False is updating an
* we are updating an existing container file. * existing container file.
* @throws StorageContainerException * @throws StorageContainerException
*/ */
private void writeToContainerFile(File containerFile, boolean isCreate) private void writeToContainerFile(File containerFile, boolean isCreate)
@ -181,19 +181,18 @@ public class KeyValueContainer implements Container {
ContainerDataYaml.createContainerFile( ContainerDataYaml.createContainerFile(
ContainerType.KeyValueContainer, containerData, tempContainerFile); ContainerType.KeyValueContainer, containerData, tempContainerFile);
// NativeIO.renameTo is an atomic function. But it might fail if the
// container file already exists. Hence, we handle the two cases
// separately.
if (isCreate) { if (isCreate) {
// When creating a new container, .container file should not exist
// already.
NativeIO.renameTo(tempContainerFile, containerFile); NativeIO.renameTo(tempContainerFile, containerFile);
} else { } else {
// When updating a container, the .container file should exist. If
// not, the container is in an inconsistent state.
Files.move(tempContainerFile.toPath(), containerFile.toPath(), Files.move(tempContainerFile.toPath(), containerFile.toPath(),
StandardCopyOption.REPLACE_EXISTING); StandardCopyOption.REPLACE_EXISTING);
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new StorageContainerException("Error during creation of " + throw new StorageContainerException("Error while creating/ updating " +
".container file. ContainerID: " + containerId, ex, ".container file. ContainerID: " + containerId, ex,
CONTAINER_FILES_CREATE_ERROR); CONTAINER_FILES_CREATE_ERROR);
} finally { } finally {
@ -206,27 +205,14 @@ public class KeyValueContainer implements Container {
} }
} }
private void createContainerFile(File containerFile)
throws StorageContainerException {
writeToContainerFile(containerFile, true);
}
private void updateContainerFile(File containerFile) private void updateContainerFile(File containerFile)
throws StorageContainerException { throws StorageContainerException {
writeToContainerFile(containerFile, false);
long containerId = containerData.getContainerID();
if (!containerFile.exists()) {
throw new StorageContainerException("Container is an Inconsistent " +
"state, missing .container file. ContainerID: " + containerId,
INVALID_CONTAINER_STATE);
}
try {
writeToContainerFile(containerFile, false);
} catch (IOException e) {
//TODO : Container update failure is not handled currently. Might
// lead to loss of .container file. When Update container feature
// support is added, this failure should also be handled.
throw new StorageContainerException("Container update failed. " +
"ContainerID: " + containerId, CONTAINER_FILES_CREATE_ERROR);
}
} }
@ -256,19 +242,15 @@ public class KeyValueContainer implements Container {
// complete this action // complete this action
try { try {
writeLock(); writeLock();
long containerId = containerData.getContainerID();
if(!containerData.isValid()) {
LOG.debug("Invalid container data. Container Id: {}", containerId);
throw new StorageContainerException("Invalid container data. " +
"ContainerID: " + containerId, INVALID_CONTAINER_STATE);
}
containerData.closeContainer(); containerData.closeContainer();
File containerFile = getContainerFile(); File containerFile = getContainerFile();
// update the new container data to .container File // update the new container data to .container File
updateContainerFile(containerFile); updateContainerFile(containerFile);
} catch (StorageContainerException ex) { } catch (StorageContainerException ex) {
// Failed to update .container file. Reset the state to CLOSING
containerData.setState(ContainerLifeCycleState.CLOSING);
throw ex; throw ex;
} finally { } finally {
writeUnlock(); writeUnlock();
@ -332,8 +314,6 @@ public class KeyValueContainer implements Container {
// update the new container data to .container File // update the new container data to .container File
updateContainerFile(containerFile); updateContainerFile(containerFile);
} catch (StorageContainerException ex) { } catch (StorageContainerException ex) {
// TODO:
// On error, reset the metadata.
containerData.setMetadata(oldMetadata); containerData.setMetadata(oldMetadata);
throw ex; throw ex;
} finally { } finally {

View File

@ -28,6 +28,8 @@ import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ContainerCommandRequestProto; .ContainerCommandRequestProto;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ContainerCommandResponseProto; .ContainerCommandResponseProto;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ContainerLifeCycleState;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ContainerType; .ContainerType;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
@ -76,6 +78,8 @@ import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.Result.CLOSED_CONTAINER_RETRY;
import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.Result.CONTAINER_INTERNAL_ERROR; .Result.CONTAINER_INTERNAL_ERROR;
import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos import static org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
@ -378,8 +382,18 @@ public class KeyValueHandler extends Handler {
return ContainerUtils.malformedRequest(request); return ContainerUtils.malformedRequest(request);
} }
long containerID = kvContainer.getContainerData().getContainerID();
ContainerLifeCycleState containerState = kvContainer.getContainerState();
try { try {
checkContainerOpen(kvContainer); if (containerState == ContainerLifeCycleState.CLOSED) {
throw new StorageContainerException("Container already closed. " +
"ContainerID: " + containerID, CLOSED_CONTAINER_RETRY);
} else if (containerState == ContainerLifeCycleState.INVALID) {
LOG.debug("Invalid container data. ContainerID: {}", containerID);
throw new StorageContainerException("Invalid container data. " +
"ContainerID: " + containerID, INVALID_CONTAINER_STATE);
}
KeyValueContainerData kvData = kvContainer.getContainerData(); KeyValueContainerData kvData = kvContainer.getContainerData();
@ -773,10 +787,9 @@ public class KeyValueHandler extends Handler {
private void checkContainerOpen(KeyValueContainer kvContainer) private void checkContainerOpen(KeyValueContainer kvContainer)
throws StorageContainerException { throws StorageContainerException {
ContainerProtos.ContainerLifeCycleState containerState = ContainerLifeCycleState containerState = kvContainer.getContainerState();
kvContainer.getContainerState();
if (containerState == ContainerProtos.ContainerLifeCycleState.OPEN) { if (containerState == ContainerLifeCycleState.OPEN) {
return; return;
} else { } else {
String msg = "Requested operation not allowed as ContainerState is " + String msg = "Requested operation not allowed as ContainerState is " +

View File

@ -33,7 +33,6 @@ import org.apache.hadoop.ozone.container.common.volume.HddsVolume;
import org.apache.hadoop.ozone.container.common.volume import org.apache.hadoop.ozone.container.common.volume
.RoundRobinVolumeChoosingPolicy; .RoundRobinVolumeChoosingPolicy;
import org.apache.hadoop.ozone.container.common.volume.VolumeSet; import org.apache.hadoop.ozone.container.common.volume.VolumeSet;
import org.apache.hadoop.ozone.container.keyvalue.helpers.KeyUtils; import org.apache.hadoop.ozone.container.keyvalue.helpers.KeyUtils;
import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.DiskChecker; import org.apache.hadoop.util.DiskChecker;
@ -242,21 +241,6 @@ public class TestKeyValueContainer {
keyValueContainerData.getState()); keyValueContainerData.getState());
} }
@Test
public void testCloseInvalidContainer() throws Exception {
try {
keyValueContainerData.setState(ContainerProtos.ContainerLifeCycleState
.INVALID);
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
keyValueContainer.close();
fail("testCloseInvalidContainer failed");
} catch (StorageContainerException ex) {
assertEquals(ContainerProtos.Result.INVALID_CONTAINER_STATE,
ex.getResult());
GenericTestUtils.assertExceptionContains("Invalid container data", ex);
}
}
@Test @Test
public void testUpdateContainer() throws IOException { public void testUpdateContainer() throws IOException {
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId); keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);

View File

@ -25,12 +25,16 @@ import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos; import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ContainerCommandRequestProto; .ContainerCommandRequestProto;
import org.apache.hadoop.hdds.scm.container.common.helpers
.StorageContainerException;
import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.ozone.container.common.helpers.ContainerMetrics; import org.apache.hadoop.ozone.container.common.helpers.ContainerMetrics;
import org.apache.hadoop.ozone.container.common.impl.ContainerSet; import org.apache.hadoop.ozone.container.common.impl.ContainerSet;
import org.apache.hadoop.ozone.container.common.impl.HddsDispatcher; import org.apache.hadoop.ozone.container.common.impl.HddsDispatcher;
import org.apache.hadoop.ozone.container.common.volume.VolumeSet; import org.apache.hadoop.ozone.container.common.volume.VolumeSet;
import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.test.GenericTestUtils;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
@ -59,8 +63,8 @@ public class TestKeyValueHandler {
@Rule @Rule
public TestRule timeout = new Timeout(300000); public TestRule timeout = new Timeout(300000);
private HddsDispatcher dispatcher; private static HddsDispatcher dispatcher;
private KeyValueHandler handler; private static KeyValueHandler handler;
private final static String DATANODE_UUID = UUID.randomUUID().toString(); private final static String DATANODE_UUID = UUID.randomUUID().toString();
@ -69,14 +73,11 @@ public class TestKeyValueHandler {
private static final long DUMMY_CONTAINER_ID = 9999; private static final long DUMMY_CONTAINER_ID = 9999;
@Test @BeforeClass
/** public static void setup() throws StorageContainerException {
* Test that Handler handles different command types correctly.
*/
public void testHandlerCommandHandling() throws Exception{
// Create mock HddsDispatcher and KeyValueHandler. // Create mock HddsDispatcher and KeyValueHandler.
this.handler = Mockito.mock(KeyValueHandler.class); handler = Mockito.mock(KeyValueHandler.class);
this.dispatcher = Mockito.mock(HddsDispatcher.class); dispatcher = Mockito.mock(HddsDispatcher.class);
Mockito.when(dispatcher.getHandler(any())).thenReturn(handler); Mockito.when(dispatcher.getHandler(any())).thenReturn(handler);
Mockito.when(dispatcher.dispatch(any())).thenCallRealMethod(); Mockito.when(dispatcher.dispatch(any())).thenCallRealMethod();
Mockito.when(dispatcher.getContainer(anyLong())).thenReturn( Mockito.when(dispatcher.getContainer(anyLong())).thenReturn(
@ -84,6 +85,13 @@ public class TestKeyValueHandler {
Mockito.when(handler.handle(any(), any())).thenCallRealMethod(); Mockito.when(handler.handle(any(), any())).thenCallRealMethod();
doCallRealMethod().when(dispatcher).setMetricsForTesting(any()); doCallRealMethod().when(dispatcher).setMetricsForTesting(any());
dispatcher.setMetricsForTesting(Mockito.mock(ContainerMetrics.class)); dispatcher.setMetricsForTesting(Mockito.mock(ContainerMetrics.class));
}
@Test
/**
* Test that Handler handles different command types correctly.
*/
public void testHandlerCommandHandling() throws Exception {
// Test Create Container Request handling // Test Create Container Request handling
ContainerCommandRequestProto createContainerRequest = ContainerCommandRequestProto createContainerRequest =
@ -250,4 +258,33 @@ public class TestKeyValueHandler {
} }
@Test
public void testCloseInvalidContainer() {
long containerID = 1234L;
Configuration conf = new Configuration();
KeyValueContainerData kvData = new KeyValueContainerData(containerID, 1);
KeyValueContainer container = new KeyValueContainer(kvData, conf);
kvData.setState(ContainerProtos.ContainerLifeCycleState.INVALID);
// Create Close container request
ContainerCommandRequestProto closeContainerRequest =
ContainerProtos.ContainerCommandRequestProto.newBuilder()
.setCmdType(ContainerProtos.Type.CloseContainer)
.setContainerID(DUMMY_CONTAINER_ID)
.setDatanodeUuid(DATANODE_UUID)
.setCloseContainer(ContainerProtos.CloseContainerRequestProto
.getDefaultInstance())
.build();
dispatcher.dispatch(closeContainerRequest);
Mockito.when(handler.handleCloseContainer(any(), any()))
.thenCallRealMethod();
// Closing invalid container should return error response.
ContainerProtos.ContainerCommandResponseProto response =
handler.handleCloseContainer(closeContainerRequest, container);
Assert.assertTrue("Close container should return Invalid container error",
response.getResult().equals(
ContainerProtos.Result.INVALID_CONTAINER_STATE));
}
} }

View File

@ -775,14 +775,6 @@ public class TestContainerPersistence {
Assert.assertEquals("bilbo_new_1", Assert.assertEquals("bilbo_new_1",
actualNewData.getMetadata().get("owner")); actualNewData.getMetadata().get("owner"));
// Update a non-existing container
exception.expect(StorageContainerException.class);
exception.expectMessage("Container is an Inconsistent " +
"state, missing .container file.");
Container nonExistentContainer = new KeyValueContainer(
new KeyValueContainerData(RandomUtils.nextLong(),
ContainerTestHelper.CONTAINER_MAX_SIZE_GB), conf);
nonExistentContainer.update(newMetadata, false);
} }
private KeyData writeKeyHelper(BlockID blockID) private KeyData writeKeyHelper(BlockID blockID)