/**
* 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.hdfs.server.namenode;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.hdfs.server.common.Util;
import org.apache.hadoop.hdfs.server.namenode.JournalStream.JournalType;
import org.apache.hadoop.hdfs.server.namenode.NNStorage.StorageLocationType;
import org.apache.hadoop.util.FlushableLogger;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.*;
public class ValidateNamespaceDirPolicy {
private static final Log LOG = LogFactory
.getLog(ValidateNamespaceDirPolicy.class.getName());
// immediate flush logger
private static final Log FLOG = FlushableLogger.getLogger(LOG);
/**
* Used for storing properties of each of the storage locations.
*/
static class NNStorageLocation {
public NNStorageLocation(URI location, String mountPoint,
StorageLocationType type) {
this.location = location;
this.mountPoint = mountPoint;
this.type = type;
}
URI location;
String mountPoint;
StorageLocationType type;
public String toString() {
return "location: " + location + ",mount point: " + mountPoint
+ ", type: " + type;
}
}
public static Map<URI, NNStorageLocation> validate(Configuration conf)
throws IOException {
Map<URI, NNStorageLocation> locationMap = new HashMap<URI, NNStorageLocation>();
int policy = conf.getInt("dfs.name.dir.policy", 0);
String nameDirConfig = "dfs.name.dir";
Collection<URI> dirNamesURIs = NNStorageConfiguration.getNamespaceDirs(conf);
validatePolicy(conf, policy, dirNamesURIs, nameDirConfig, locationMap);
String nameEditsDirConfig = "dfs.name.edits.dir";
Collection<URI> editsDirNamesURIs = NNStorageConfiguration.getNamespaceEditsDirs(conf);
validatePolicy(conf, policy, editsDirNamesURIs, nameEditsDirConfig, locationMap);
return locationMap;
}
static void validatePolicy(Configuration conf,
int policy,
Collection<URI> configuredLocations,
String configName,
Map<URI, NNStorageLocation> result)
throws IOException {
/* DFS name node directory policy:
0 - No enforcement
1 - Enforce that there should be at least two copies and they must be on
different devices
2 - Enforce that there should be at least two copies on different devices
and at least one must be on an NFS/remote device
*/
// convert uri's for directory names
List<NNStorageLocation> locations = new ArrayList<NNStorageLocation>();
String shared = conf.get(configName + ".shared");
URI sharedLocation = shared == null ? null : Util.stringAsURI(shared);
for (URI name : configuredLocations) {
FLOG.info("Conf validation - checking location: " + name);
NNStorageLocation desc = checkLocation(name, conf, sharedLocation);
locations.add(desc);
result.put(desc.location, desc);
FLOG.info("Conf validation - checked location: " + desc);
}
switch (policy) {
case 0:
// No check needed.
break;
case 1:
case 2:
boolean foundRemote = false;
HashSet<String> mountPoints = new HashSet<String>();
// Check that there should be at least two copies
if (locations.size() < 2) {
throw new IOException("Configuration parameter " + configName
+ " violated DFS name node directory policy:"
+ " There should be at least two copies.");
}
for (NNStorageLocation location : locations) {
foundRemote |= (location.type == StorageLocationType.REMOTE
|| location.type == StorageLocationType.SHARED);
mountPoints.add(location.mountPoint);
}
// Check that there should be at least two directories on different
// mount points
if (mountPoints.size() < 2) {
throw new IOException("Configuration parameter " + configName
+ " violated DFS name node directory policy:"
+ " There must be at least two copies on different"
+ " devices");
}
// If policy is 2, check that at least one directory is on NFS device
if (policy == 2 && !foundRemote) {
throw new IOException("Configuration parameter " + configName
+ " violated DFS name node directory policy:"
+ " There must be at least one copy on an NFS/remote device");
}
break;
default:
throw new IOException(
"Unexpected configuration parameters: dfs.name.dir.policy = "
+ policy
+ ", must be between 0 and 2.");
}
}
private static NNStorageLocation checkLocation(URI location,
Configuration conf, URI sharedLocation) throws IOException {
// check shared locations (for file and non-file locations
boolean isShared = false;
if (sharedLocation != null) {
// check if the location is shared
isShared = location.equals(sharedLocation);
}
// handle non-file locations
if ((location.getScheme().compareTo(JournalType.FILE.name().toLowerCase()) != 0)) {
// non-file locations are all remote - we might want to add more checks in the future
// mount point is set to the uri scheme
return new NNStorageLocation(location,
location.getScheme().toLowerCase(),
isShared ? StorageLocationType.SHARED : StorageLocationType.REMOTE);
} else {
// enforce existence
checkDirectory(location.getPath());
try {
return getInfoUnix(location, isShared);
} catch (Exception e) {
FLOG.info("Failed to fetch information with unix based df", e);
}
try {
return getInfoMacOS(location, isShared);
} catch (Exception e) {
FLOG.info("Failed to fetch information with macos based df", e);
}
throw new IOException("Failed to run df");
}
}
private static NNStorageLocation getInfoUnix(URI location, boolean isShared)
throws Exception {
String command = "df -P -T " + location.getPath();
String[] fields = execCommand(command);
if (fields.length < 2)
throw new IOException("Unexpected output from command ' " + command);
// check if the location is remote
boolean isRemote = fields[1].equals("nfs");
StorageLocationType type = isShared ? StorageLocationType.SHARED :
(isRemote ? StorageLocationType.REMOTE : StorageLocationType.LOCAL);
// "Type" is the second column, and "Mounted on" is the last column
return new NNStorageLocation(location, fields[fields.length - 1], type);
}
private static NNStorageLocation getInfoMacOS(URI location, boolean isShared)
throws Exception {
String command = "df -P " + location.getPath();
String[] fields = execCommand(command);
if (fields.length < 2)
throw new IOException("Unexpected output from command" + command);
String mountPoint = fields[fields.length - 1];
// check if the location is remote
command = "df -P -T nfs" + location.getPath();
fields = execCommand(command);
// if there is output then the path is mounted on nfs
boolean isRemote = (fields.length == 0) ? false
: true;
StorageLocationType type = isShared ? StorageLocationType.SHARED :
(isRemote ? StorageLocationType.REMOTE : StorageLocationType.LOCAL);
// "Type" is the second column, and "Mounted on" is the last column
return new NNStorageLocation(location, mountPoint, type);
}
private static String[] execCommand(String command) throws IOException {
Process p = Runtime.getRuntime().exec(command);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Skip the first line, which is the header info
br.readLine();
String output = br.readLine();
FLOG.info("Running: " + command + ", output: " + output);
if (output == null || output.isEmpty()) {
return new String[0];
}
return output.split("\\s+");
}
private static void checkDirectory(String name) throws IOException {
// check existence
File dir = new File(name);
if (!dir.exists() || !dir.isDirectory()) {
throw new IOException("Validation failed for " + name
+ ". Storage directory does not exist, or is not a directory");
}
}
}