blob: 7992dad78db5a40547fb4d3be4a2d248b178b331 [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.common;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeType;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
/**
* Storage information file. This Class defines the methods to check
* the consistency of the storage dir and the version file.
* <p>
* Local storage information is stored in a separate file VERSION.
* It contains type of the node,
* the storage layout version, the SCM id, and
* the OM/SCM state creation time.
*
*/
@InterfaceAudience.Private
public abstract class Storage {
private static final Logger LOG = LoggerFactory.getLogger(Storage.class);
public static final String STORAGE_DIR_CURRENT = "current";
protected static final String STORAGE_FILE_VERSION = "VERSION";
public static final String CONTAINER_DIR = "containerDir";
private final NodeType nodeType;
private final File root;
private final File storageDir;
private StorageState state;
private StorageInfo storageInfo;
/**
* Determines the state of the Version file.
*/
public enum StorageState {
NON_EXISTENT, NOT_INITIALIZED, INITIALIZED
}
public Storage(NodeType type, File root, String sdName)
throws IOException {
this.nodeType = type;
this.root = root;
this.storageDir = new File(root, sdName);
this.state = getStorageState();
if (state == StorageState.INITIALIZED) {
this.storageInfo = new StorageInfo(type, getVersionFile());
} else {
this.storageInfo = new StorageInfo(
nodeType, StorageInfo.newClusterID(), Time.now());
setNodeProperties();
}
}
/**
* Gets the path of the Storage dir.
* @return Storage dir path
*/
public String getStorageDir() {
return storageDir.getAbsoluteFile().toString();
}
/**
* Gets the state of the version file.
* @return the state of the Version file
*/
public StorageState getState() {
return state;
}
public NodeType getNodeType() {
return storageInfo.getNodeType();
}
public String getClusterID() {
return storageInfo.getClusterID();
}
public long getCreationTime() {
return storageInfo.getCreationTime();
}
public void setClusterId(String clusterId) throws IOException {
if (state == StorageState.INITIALIZED) {
throw new IOException(
"Storage directory " + storageDir + " already initialized.");
} else {
storageInfo.setClusterId(clusterId);
}
}
/**
* Retrieves the storageInfo instance to read/write the common
* version file properties.
* @return the instance of the storageInfo class
*/
protected StorageInfo getStorageInfo() {
return storageInfo;
}
abstract protected Properties getNodeProperties();
/**
* Sets the Node properties specific to OM/SCM.
*/
private void setNodeProperties() {
Properties nodeProperties = getNodeProperties();
if (nodeProperties != null) {
for (String key : nodeProperties.stringPropertyNames()) {
storageInfo.setProperty(key, nodeProperties.getProperty(key));
}
}
}
/**
* Directory {@code current} contains latest files defining
* the file system meta-data.
*
* @return the directory path
*/
public File getCurrentDir() {
return new File(storageDir, STORAGE_DIR_CURRENT);
}
/**
* File {@code VERSION} contains the following fields:
* <ol>
* <li>node type</li>
* <li>OM/SCM state creation time</li>
* <li>other fields specific for this node type</li>
* </ol>
* The version file is always written last during storage directory updates.
* The existence of the version file indicates that all other files have
* been successfully written in the storage directory, the storage is valid
* and does not need to be recovered.
*
* @return the version file path
*/
private File getVersionFile() {
return new File(getCurrentDir(), STORAGE_FILE_VERSION);
}
/**
* Check to see if current/ directory is empty. This method is used
* before determining to format the directory.
* @throws IOException if unable to list files under the directory.
*/
private void checkEmptyCurrent() throws IOException {
File currentDir = getCurrentDir();
if (!currentDir.exists()) {
// if current/ does not exist, it's safe to format it.
return;
}
try (DirectoryStream<Path> dirStream = Files
.newDirectoryStream(currentDir.toPath())) {
if (dirStream.iterator().hasNext()) {
throw new InconsistentStorageStateException(getCurrentDir(),
"Can't initialize the storage directory because the current "
+ "it is not empty.");
}
}
}
/**
* Check consistency of the storage directory.
*
* @return state {@link StorageState} of the storage directory
* @throws IOException
*/
private StorageState getStorageState() throws IOException {
assert root != null : "root is null";
String rootPath = root.getCanonicalPath();
try { // check that storage exists
if (!root.exists()) {
// storage directory does not exist
LOG.warn("Storage directory " + rootPath + " does not exist");
return StorageState.NON_EXISTENT;
}
// or is inaccessible
if (!root.isDirectory()) {
LOG.warn(rootPath + "is not a directory");
return StorageState.NON_EXISTENT;
}
if (!FileUtil.canWrite(root)) {
LOG.warn("Cannot access storage directory " + rootPath);
return StorageState.NON_EXISTENT;
}
} catch (SecurityException ex) {
LOG.warn("Cannot access storage directory " + rootPath, ex);
return StorageState.NON_EXISTENT;
}
// check whether current directory is valid
File versionFile = getVersionFile();
boolean hasCurrent = versionFile.exists();
if (hasCurrent) {
return StorageState.INITIALIZED;
} else {
checkEmptyCurrent();
return StorageState.NOT_INITIALIZED;
}
}
/**
* Creates the Version file if not present,
* otherwise returns with IOException.
* @throws IOException
*/
public void initialize() throws IOException {
if (state == StorageState.INITIALIZED) {
throw new IOException("Storage directory already initialized.");
}
if (!getCurrentDir().mkdirs()) {
throw new IOException("Cannot create directory " + getCurrentDir());
}
storageInfo.writeTo(getVersionFile());
}
/**
* Persists current StorageInfo to file system..
* @throws IOException
*/
public void persistCurrentState() throws IOException {
if (!getCurrentDir().exists()) {
throw new IOException("Metadata dir doesn't exist, dir: " +
getCurrentDir());
}
storageInfo.writeTo(getVersionFile());
}
}