blob: 81d3065833ebe34c429f9efa334e7feb7a63637e [file] [log] [blame]
/**
* 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.ozone.container.keyvalue;
import com.google.common.primitives.Longs;
import org.apache.hadoop.conf.StorageUnit;
import org.apache.hadoop.hdds.client.BlockID;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.scm.container.common.helpers
.StorageContainerException;
import org.apache.hadoop.ozone.container.common.helpers.BlockData;
import org.apache.hadoop.ozone.container.common.helpers.ChunkInfo;
import org.apache.hadoop.ozone.container.common.impl.ContainerDataYaml;
import org.apache.hadoop.ozone.container.common.volume.HddsVolume;
import org.apache.hadoop.ozone.container.common.volume
.RoundRobinVolumeChoosingPolicy;
import org.apache.hadoop.ozone.container.common.volume.VolumeSet;
import org.apache.hadoop.ozone.container.keyvalue.helpers.BlockUtils;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.DiskChecker;
import org.apache.hadoop.ozone.container.common.utils.ReferenceCountedDB;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.UUID;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.ratis.util.Preconditions.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
/**
* Class to test KeyValue Container operations.
*/
public class TestKeyValueContainer {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private OzoneConfiguration conf;
private String scmId = UUID.randomUUID().toString();
private VolumeSet volumeSet;
private RoundRobinVolumeChoosingPolicy volumeChoosingPolicy;
private KeyValueContainerData keyValueContainerData;
private KeyValueContainer keyValueContainer;
private UUID datanodeId;
@Before
public void setUp() throws Exception {
conf = new OzoneConfiguration();
datanodeId = UUID.randomUUID();
HddsVolume hddsVolume = new HddsVolume.Builder(folder.getRoot()
.getAbsolutePath()).conf(conf).datanodeUuid(datanodeId
.toString()).build();
volumeSet = mock(VolumeSet.class);
volumeChoosingPolicy = mock(RoundRobinVolumeChoosingPolicy.class);
Mockito.when(volumeChoosingPolicy.chooseVolume(anyList(), anyLong()))
.thenReturn(hddsVolume);
keyValueContainerData = new KeyValueContainerData(1L,
(long) StorageUnit.GB.toBytes(5), UUID.randomUUID().toString(),
datanodeId.toString());
keyValueContainer = new KeyValueContainer(
keyValueContainerData, conf);
}
@Test
public void testBlockIterator() throws Exception{
keyValueContainerData = new KeyValueContainerData(100L,
(long) StorageUnit.GB.toBytes(1), UUID.randomUUID().toString(),
datanodeId.toString());
keyValueContainer = new KeyValueContainer(
keyValueContainerData, conf);
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
KeyValueBlockIterator blockIterator = keyValueContainer.blockIterator();
//As no blocks created, hasNext should return false.
assertFalse(blockIterator.hasNext());
int blockCount = 10;
addBlocks(blockCount);
blockIterator = keyValueContainer.blockIterator();
assertTrue(blockIterator.hasNext());
BlockData blockData;
int blockCounter = 0;
while(blockIterator.hasNext()) {
blockData = blockIterator.nextBlock();
assertEquals(blockCounter++, blockData.getBlockID().getLocalID());
}
assertEquals(blockCount, blockCounter);
}
private void addBlocks(int count) throws Exception {
long containerId = keyValueContainerData.getContainerID();
try(ReferenceCountedDB metadataStore = BlockUtils.getDB(keyValueContainer
.getContainerData(), conf)) {
for (int i = 0; i < count; i++) {
// Creating BlockData
BlockID blockID = new BlockID(containerId, i);
BlockData blockData = new BlockData(blockID);
blockData.addMetadata("VOLUME", "ozone");
blockData.addMetadata("OWNER", "hdfs");
List<ContainerProtos.ChunkInfo> chunkList = new ArrayList<>();
ChunkInfo info = new ChunkInfo(String.format("%d.data.%d", blockID
.getLocalID(), 0), 0, 1024);
chunkList.add(info.getProtoBufMessage());
blockData.setChunks(chunkList);
metadataStore.getStore().put(Longs.toByteArray(blockID.getLocalID()),
blockData
.getProtoBufMessage().toByteArray());
}
}
}
@SuppressWarnings("RedundantCast")
@Test
public void testCreateContainer() throws Exception {
// Create Container.
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
keyValueContainerData = keyValueContainer
.getContainerData();
String containerMetaDataPath = keyValueContainerData
.getMetadataPath();
String chunksPath = keyValueContainerData.getChunksPath();
// Check whether containerMetaDataPath and chunksPath exists or not.
assertTrue(containerMetaDataPath != null);
assertTrue(chunksPath != null);
//Check whether container file and container db file exists or not.
assertTrue(keyValueContainer.getContainerFile().exists(),
".Container File does not exist");
assertTrue(keyValueContainer.getContainerDBFile().exists(), "Container " +
"DB does not exist");
}
@Test
public void testContainerImportExport() throws Exception {
long containerId = keyValueContainer.getContainerData().getContainerID();
// Create Container.
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
keyValueContainerData = keyValueContainer
.getContainerData();
keyValueContainerData.setState(
ContainerProtos.ContainerDataProto.State.CLOSED);
int numberOfKeysToWrite = 12;
//write one few keys to check the key count after import
try(ReferenceCountedDB metadataStore =
BlockUtils.getDB(keyValueContainerData, conf)) {
for (int i = 0; i < numberOfKeysToWrite; i++) {
metadataStore.getStore().put(("test" + i).getBytes(UTF_8),
"test".getBytes(UTF_8));
}
}
BlockUtils.removeDB(keyValueContainerData, conf);
Map<String, String> metadata = new HashMap<>();
metadata.put("key1", "value1");
keyValueContainer.update(metadata, true);
//destination path
File folderToExport = folder.newFile("exported.tar.gz");
TarContainerPacker packer = new TarContainerPacker();
//export the container
try (FileOutputStream fos = new FileOutputStream(folderToExport)) {
keyValueContainer
.exportContainerData(fos, packer);
}
//delete the original one
keyValueContainer.delete();
//create a new one
KeyValueContainerData containerData =
new KeyValueContainerData(containerId, 1,
keyValueContainerData.getMaxSize(), UUID.randomUUID().toString(),
datanodeId.toString());
KeyValueContainer container = new KeyValueContainer(containerData, conf);
HddsVolume containerVolume = volumeChoosingPolicy.chooseVolume(volumeSet
.getVolumesList(), 1);
String hddsVolumeDir = containerVolume.getHddsRootDir().toString();
container.populatePathFields(scmId, containerVolume, hddsVolumeDir);
try (FileInputStream fis = new FileInputStream(folderToExport)) {
container.importContainerData(fis, packer);
}
Assert.assertEquals("value1", containerData.getMetadata().get("key1"));
Assert.assertEquals(keyValueContainerData.getContainerDBType(),
containerData.getContainerDBType());
Assert.assertEquals(keyValueContainerData.getState(),
containerData.getState());
Assert.assertEquals(numberOfKeysToWrite,
containerData.getKeyCount());
Assert.assertEquals(keyValueContainerData.getLayOutVersion(),
containerData.getLayOutVersion());
Assert.assertEquals(keyValueContainerData.getMaxSize(),
containerData.getMaxSize());
Assert.assertEquals(keyValueContainerData.getBytesUsed(),
containerData.getBytesUsed());
//Can't overwrite existing container
try {
try (FileInputStream fis = new FileInputStream(folderToExport)) {
container.importContainerData(fis, packer);
}
fail("Container is imported twice. Previous files are overwritten");
} catch (IOException ex) {
//all good
}
}
@Test
public void testDuplicateContainer() throws Exception {
try {
// Create Container.
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
fail("testDuplicateContainer failed");
} catch (StorageContainerException ex) {
GenericTestUtils.assertExceptionContains("ContainerFile already " +
"exists", ex);
assertEquals(ContainerProtos.Result.CONTAINER_ALREADY_EXISTS, ex
.getResult());
}
}
@Test
public void testDiskFullExceptionCreateContainer() throws Exception {
Mockito.when(volumeChoosingPolicy.chooseVolume(anyList(), anyLong()))
.thenThrow(DiskChecker.DiskOutOfSpaceException.class);
try {
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
fail("testDiskFullExceptionCreateContainer failed");
} catch (StorageContainerException ex) {
GenericTestUtils.assertExceptionContains("disk out of space",
ex);
assertEquals(ContainerProtos.Result.DISK_OUT_OF_SPACE, ex.getResult());
}
}
@Test
public void testDeleteContainer() throws Exception {
keyValueContainerData.setState(ContainerProtos.ContainerDataProto.State
.CLOSED);
keyValueContainer = new KeyValueContainer(
keyValueContainerData, conf);
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
keyValueContainer.delete();
String containerMetaDataPath = keyValueContainerData
.getMetadataPath();
File containerMetaDataLoc = new File(containerMetaDataPath);
assertFalse("Container directory still exists", containerMetaDataLoc
.getParentFile().exists());
assertFalse("Container File still exists",
keyValueContainer.getContainerFile().exists());
assertFalse("Container DB file still exists",
keyValueContainer.getContainerDBFile().exists());
}
@Test
public void testCloseContainer() throws Exception {
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
keyValueContainer.close();
keyValueContainerData = keyValueContainer
.getContainerData();
assertEquals(ContainerProtos.ContainerDataProto.State.CLOSED,
keyValueContainerData.getState());
//Check state in the .container file
String containerMetaDataPath = keyValueContainerData
.getMetadataPath();
File containerFile = keyValueContainer.getContainerFile();
keyValueContainerData = (KeyValueContainerData) ContainerDataYaml
.readContainerFile(containerFile);
assertEquals(ContainerProtos.ContainerDataProto.State.CLOSED,
keyValueContainerData.getState());
}
@Test
public void testReportOfUnhealthyContainer() throws Exception {
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
Assert.assertNotNull(keyValueContainer.getContainerReport());
keyValueContainer.markContainerUnhealthy();
File containerFile = keyValueContainer.getContainerFile();
keyValueContainerData = (KeyValueContainerData) ContainerDataYaml
.readContainerFile(containerFile);
assertEquals(ContainerProtos.ContainerDataProto.State.UNHEALTHY,
keyValueContainerData.getState());
Assert.assertNotNull(keyValueContainer.getContainerReport());
}
@Test
public void testUpdateContainer() throws IOException {
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
Map<String, String> metadata = new HashMap<>();
metadata.put("VOLUME", "ozone");
metadata.put("OWNER", "hdfs");
keyValueContainer.update(metadata, true);
keyValueContainerData = keyValueContainer
.getContainerData();
assertEquals(2, keyValueContainerData.getMetadata().size());
//Check metadata in the .container file
File containerFile = keyValueContainer.getContainerFile();
keyValueContainerData = (KeyValueContainerData) ContainerDataYaml
.readContainerFile(containerFile);
assertEquals(2, keyValueContainerData.getMetadata().size());
}
@Test
public void testUpdateContainerUnsupportedRequest() throws Exception {
try {
keyValueContainerData.setState(
ContainerProtos.ContainerDataProto.State.CLOSED);
keyValueContainer = new KeyValueContainer(keyValueContainerData, conf);
keyValueContainer.create(volumeSet, volumeChoosingPolicy, scmId);
Map<String, String> metadata = new HashMap<>();
metadata.put("VOLUME", "ozone");
keyValueContainer.update(metadata, false);
fail("testUpdateContainerUnsupportedRequest failed");
} catch (StorageContainerException ex) {
GenericTestUtils.assertExceptionContains("Updating a closed container " +
"without force option is not allowed", ex);
assertEquals(ContainerProtos.Result.UNSUPPORTED_REQUEST, ex
.getResult());
}
}
}