blob: 100e3da1cfce54fb87deffa590b2106f1a47a92f [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.common.volume;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.annotation.InterfaceStability;
import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
import org.apache.hadoop.ozone.container.common.utils.DatanodeStoreCache;
import org.apache.hadoop.ozone.container.common.utils.HddsVolumeUtil;
import org.apache.hadoop.ozone.container.common.utils.StorageVolumeUtil;
import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures;
import org.apache.hadoop.ozone.container.upgrade.VersionedDatanodeFeatures.SchemaV3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.hadoop.ozone.OzoneConsts.CONTAINER_DB_NAME;
import static org.apache.hadoop.ozone.container.common.utils.HddsVolumeUtil.initPerDiskDBStore;
/**
* HddsVolume represents volume in a datanode. {@link MutableVolumeSet}
* maintains a list of HddsVolumes, one for each volume in the Datanode.
* {@link VolumeInfo} in encompassed by this class.
* <p>
* The disk layout per volume is as follows:
* <p>../hdds/VERSION
* <p>{@literal ../hdds/<<clusterUuid>>/current/<<containerDir>>/<<containerID
* >>/metadata}
* <p>{@literal ../hdds/<<clusterUuid>>/current/<<containerDir>>/<<containerID
* >>/<<dataDir>>}
* <p>
* Each hdds volume has its own VERSION file. The hdds volume will have one
* clusterUuid directory for each SCM it is a part of (currently only one SCM is
* supported).
* <p>
* During DN startup, if the VERSION file exists, we verify that the
* clusterID in the version file matches the clusterID from SCM.
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
@SuppressWarnings("finalclass")
public class HddsVolume extends StorageVolume {
private static final Logger LOG = LoggerFactory.getLogger(HddsVolume.class);
public static final String HDDS_VOLUME_DIR = "hdds";
private final VolumeIOStats volumeIOStats;
private final VolumeInfoMetrics volumeInfoMetrics;
private final AtomicLong committedBytes; // till Open containers become full
// Mentions the type of volume
private final VolumeType type = VolumeType.DATA_VOLUME;
// The dedicated DbVolume that the db instance of this HddsVolume resides.
// This is optional, if null then the db instance resides on this HddsVolume.
private DbVolume dbVolume;
// The subdirectory with storageID as its name, used to build the
// container db path. This is initialized only once together with dbVolume,
// and stored as a member to prevent spawning lots of File objects.
private File dbParentDir;
private AtomicBoolean dbLoaded = new AtomicBoolean(false);
/**
* Builder for HddsVolume.
*/
public static class Builder extends StorageVolume.Builder<Builder> {
public Builder(String volumeRootStr) {
super(volumeRootStr, HDDS_VOLUME_DIR);
}
@Override
public Builder getThis() {
return this;
}
public HddsVolume build() throws IOException {
return new HddsVolume(this);
}
}
private HddsVolume(Builder b) throws IOException {
super(b);
if (!b.getFailedVolume()) {
this.setState(VolumeState.NOT_INITIALIZED);
this.volumeIOStats = new VolumeIOStats(b.getVolumeRootStr(),
this.getStorageDir().toString());
this.volumeInfoMetrics =
new VolumeInfoMetrics(b.getVolumeRootStr(), this);
this.committedBytes = new AtomicLong(0);
LOG.info("Creating HddsVolume: {} of storage type : {} capacity : {}",
getStorageDir(), b.getStorageType(), getVolumeInfo().getCapacity());
initialize();
} else {
// Builder is called with failedVolume set, so create a failed volume
// HddsVolume Object.
this.setState(VolumeState.FAILED);
volumeIOStats = null;
volumeInfoMetrics = null;
committedBytes = null;
}
}
@Override
public void createWorkingDir(String workingDirName,
MutableVolumeSet dbVolumeSet) throws IOException {
super.createWorkingDir(workingDirName, dbVolumeSet);
// Create DB store for a newly formatted volume
if (VersionedDatanodeFeatures.isFinalized(
HDDSLayoutFeature.DATANODE_SCHEMA_V3)) {
createDbStore(dbVolumeSet);
}
}
public File getHddsRootDir() {
return super.getStorageDir();
}
public VolumeType getType() {
return type;
}
public VolumeIOStats getVolumeIOStats() {
return volumeIOStats;
}
public VolumeInfoMetrics getVolumeInfoStats() {
return volumeInfoMetrics;
}
@Override
public void failVolume() {
super.failVolume();
if (volumeIOStats != null) {
volumeIOStats.unregister();
}
if (volumeInfoMetrics != null) {
volumeInfoMetrics.unregister();
}
closeDbStore();
}
@Override
public void shutdown() {
super.shutdown();
if (volumeIOStats != null) {
volumeIOStats.unregister();
}
if (volumeInfoMetrics != null) {
volumeInfoMetrics.unregister();
}
closeDbStore();
}
/**
* add "delta" bytes to committed space in the volume.
*
* @param delta bytes to add to committed space counter
* @return bytes of committed space
*/
public long incCommittedBytes(long delta) {
return committedBytes.addAndGet(delta);
}
/**
* return the committed space in the volume.
*
* @return bytes of committed space
*/
public long getCommittedBytes() {
return committedBytes.get();
}
public void setDbVolume(DbVolume dbVolume) {
this.dbVolume = dbVolume;
}
public DbVolume getDbVolume() {
return this.dbVolume;
}
public File getDbParentDir() {
return this.dbParentDir;
}
public boolean isDbLoaded() {
return dbLoaded.get();
}
public void loadDbStore(boolean readOnly) throws IOException {
// DN startup for the first time, not registered yet,
// so the DbVolume is not formatted.
if (!getStorageState().equals(VolumeState.NORMAL)) {
return;
}
// DB is already loaded
if (dbLoaded.get()) {
LOG.warn("Schema V3 db is already loaded from {} for volume {}",
getDbParentDir(), getStorageID());
return;
}
File clusterIdDir = new File(dbVolume == null ?
getStorageDir() : dbVolume.getStorageDir(),
getClusterID());
if (!clusterIdDir.exists()) {
throw new IOException("Working dir " + clusterIdDir.getAbsolutePath() +
" not created for HddsVolume: " + getStorageDir().getAbsolutePath());
}
File storageIdDir = new File(clusterIdDir, getStorageID());
if (!storageIdDir.exists()) {
throw new IOException("Db parent dir " + storageIdDir.getAbsolutePath() +
" not found for HddsVolume: " + getStorageDir().getAbsolutePath());
}
File containerDBFile = new File(storageIdDir, CONTAINER_DB_NAME);
if (!containerDBFile.exists()) {
throw new IOException("Db dir " + storageIdDir.getAbsolutePath() +
" not found for HddsVolume: " + getStorageDir().getAbsolutePath());
}
String containerDBPath = containerDBFile.getAbsolutePath();
try {
initPerDiskDBStore(containerDBPath, getConf(), readOnly);
} catch (IOException e) {
throw new IOException("Can't init db instance under path "
+ containerDBPath + " for volume " + getStorageID(), e);
}
dbParentDir = storageIdDir;
dbLoaded.set(true);
LOG.info("SchemaV3 db is loaded at {} for volume {}", containerDBPath,
getStorageID());
}
/**
* Pick a DbVolume for HddsVolume and init db instance.
* Use the HddsVolume directly if no DbVolume found.
* @param dbVolumeSet
*/
public void createDbStore(MutableVolumeSet dbVolumeSet) throws IOException {
DbVolume chosenDbVolume = null;
File clusterIdDir;
String workingDir = getWorkingDir() == null ? getClusterID() :
getWorkingDir();
if (dbVolumeSet == null || dbVolumeSet.getVolumesList().isEmpty()) {
// No extra db volumes specified, just create db under the HddsVolume.
clusterIdDir = new File(getStorageDir(), workingDir);
} else {
// Randomly choose a DbVolume for simplicity.
List<DbVolume> dbVolumeList = StorageVolumeUtil.getDbVolumesList(
dbVolumeSet.getVolumesList());
chosenDbVolume = dbVolumeList.get(
ThreadLocalRandom.current().nextInt(dbVolumeList.size()));
clusterIdDir = new File(chosenDbVolume.getStorageDir(), workingDir);
}
if (!clusterIdDir.exists()) {
throw new IOException("The working dir "
+ clusterIdDir.getAbsolutePath() + " is missing for volume "
+ getStorageID());
}
// Init subdir with the storageID of HddsVolume.
File storageIdDir = new File(clusterIdDir, getStorageID());
if (!storageIdDir.mkdirs() && !storageIdDir.exists()) {
throw new IOException("Can't make subdir under "
+ clusterIdDir.getAbsolutePath() + " for volume "
+ getStorageID());
}
// Create the db instance for HddsVolume under the subdir above.
String containerDBPath = new File(storageIdDir, CONTAINER_DB_NAME)
.getAbsolutePath();
try {
HddsVolumeUtil.initPerDiskDBStore(containerDBPath, getConf(), false);
dbLoaded.set(true);
LOG.info("SchemaV3 db is created and loaded at {} for volume {}",
containerDBPath, getStorageID());
} catch (IOException e) {
String errMsg = "Can't create db instance under path "
+ containerDBPath + " for volume " + getStorageID();
LOG.error(errMsg, e);
throw new IOException(errMsg);
}
// Set the dbVolume and dbParentDir of the HddsVolume for db path lookup.
dbVolume = chosenDbVolume;
dbParentDir = storageIdDir;
if (chosenDbVolume != null) {
chosenDbVolume.addHddsDbStorePath(getStorageID(), containerDBPath);
}
// If SchemaV3 is disabled, close the DB instance
if (!SchemaV3.isFinalizedAndEnabled(getConf())) {
closeDbStore();
}
}
private void closeDbStore() {
if (!dbLoaded.get()) {
return;
}
String containerDBPath = new File(dbParentDir, CONTAINER_DB_NAME)
.getAbsolutePath();
DatanodeStoreCache.getInstance().removeDB(containerDBPath);
dbLoaded.set(false);
LOG.info("SchemaV3 db is stopped at {} for volume {}", containerDBPath,
getStorageID());
}
}