/*******************************************************************************
* Copyright (c) 2012 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed 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.cloudifysource.utilitydomain.context.blockstorage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.LogOutputStream;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.cloudifysource.domain.context.blockstorage.LocalStorageOperationException;
import org.cloudifysource.domain.context.blockstorage.StorageFacade;
import org.hyperic.sigar.FileSystem;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
import com.gigaspaces.internal.sigar.SigarHolder;
/**
*
* @author elip
*
*/
public final class VolumeUtils {
// prevent instantiation.
private VolumeUtils() {
}
private static final Logger logger = Logger.getLogger(VolumeUtils.class.getName());
private static final String USER_NAME = System.getProperty("user.name");
private static final long MOUNT_TIMEOUT = 30 * 1000;
private static final long FORMAT_TIMEOUT = 5 * 60 * 1000;
private static final long PARTITION_TIMEOUT = 30 * 1000;
private static final long UNMOUNT_TIMEOUT = 15 * 1000;
private static final long TEN_SECONDS = 10 * 1000;
/**
* @see {@link StorageFacade#unmount(String, long)}.
* @param device .
* @param timeoutInMillis .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void unmount(final String device, final long timeoutInMillis)
throws LocalStorageOperationException, TimeoutException {
executeCommandLine("sudo umount -d -v -l -f " + device, timeoutInMillis);
}
/**
* @see {@link StorageFacade#unmount(String)}
* @param device .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void unmount(final String device) throws LocalStorageOperationException, TimeoutException {
unmount(device, UNMOUNT_TIMEOUT);
}
/**
* @see {@link StorageFacade#mount(String, String, long)}
* @param device .
* @param path .
* @param timeoutInMillis .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void mount(final String device, final String path, final long timeoutInMillis)
throws LocalStorageOperationException, TimeoutException {
File devicePath = new File(path);
executeCommandLine("sudo mkdir " + path, TEN_SECONDS);
executeCommandLine("sudo mount " + device + " " + path, timeoutInMillis);
executeCommandLine("sudo chown " + USER_NAME + " " + devicePath.getAbsolutePath(), TEN_SECONDS);
}
/**
* @see {@link StorageFacade#mount(String, String)}
* @param device .
* @param path .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void mount(final String device, final String path)
throws LocalStorageOperationException, TimeoutException {
mount(device, path, MOUNT_TIMEOUT);
}
/**
* @see {@link StorageFacade#format(String, String, long)}
* @param device .
* @param fileSystem .
* @param timeoutInMillis .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void format(final String device, final String fileSystem, final long timeoutInMillis)
throws LocalStorageOperationException, TimeoutException {
checkFileSystemSupported(fileSystem);
executeCommandLine("sudo mkfs -t " + fileSystem + " " + device, timeoutInMillis);
}
/**
* @see {@link StorageFacade#format(String, String)}.
* @param device .
* @param fileSystem .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void format(final String device, final String fileSystem)
throws LocalStorageOperationException, TimeoutException {
format(device, fileSystem, FORMAT_TIMEOUT);
}
/**
* @see {@link StorageFacade#partition(String, long)}
* @param device .
* @param timeoutInMillis .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void partition(final String device, final long timeoutInMillis)
throws LocalStorageOperationException, TimeoutException {
try {
File tempParitioningScript = File.createTempFile("partitionvolume", ".sh");
FileUtils.writeStringToFile(tempParitioningScript,
"(echo o; echo n; echo p; echo 1; echo; echo; echo w) | sudo fdisk " + device);
tempParitioningScript.setExecutable(true);
tempParitioningScript.deleteOnExit();
executeCommandLine(tempParitioningScript.getAbsolutePath(), timeoutInMillis);
} catch (IOException ioe) {
// fdisk returns exit code 1 even when successful so we have to verify
logger.info("inspecting fdisk command exception: " + ioe.getMessage());
if (!verifyDevicePartitioning(device, timeoutInMillis)) {
throw new LocalStorageOperationException("Failed to partition device " + device + ", reported error: "
+ ioe.getMessage(), ioe);
}
}
}
private static boolean verifyDevicePartitioning(final String device, final long timeoutInMillis)
throws LocalStorageOperationException, TimeoutException {
boolean deviceFormatted = false;
try {
File tempDeviceListScript = File.createTempFile("getPartitionStatus", ".sh");
FileUtils.writeStringToFile(tempDeviceListScript, "sudo fdisk -l | grep " + device);
tempDeviceListScript.setExecutable(true);
tempDeviceListScript.deleteOnExit();
String fdiskOutput = executeSilentCommandLineReturnOutput(tempDeviceListScript.getAbsolutePath(), timeoutInMillis);
if (fdiskOutput.contains(device + ":") &&
!fdiskOutput.contains(device + " doesn't contain a valid partition table")) {
deviceFormatted = true;
}
} catch (Exception e) {
throw new LocalStorageOperationException("Failed verifying paritioning of device: " + device, e);
}
return deviceFormatted;
}
/**
* @see {@link StorageFacade#partition(String)}.
* @param device .
* @throws LocalStorageOperationException .
* @throws TimeoutException .
*/
public static void partition(final String device)
throws LocalStorageOperationException, TimeoutException {
partition(device, PARTITION_TIMEOUT);
}
private static void checkFileSystemSupported(final String fileSystem) throws LocalStorageOperationException {
FileSystem[] fileSystemList;
try {
Sigar sigar = SigarHolder.getSigar();
fileSystemList = sigar.getFileSystemList();
} catch (final SigarException e) {
throw new LocalStorageOperationException(e);
}
List<String> supportedFileSystems = new ArrayList<String>();
for (FileSystem fs : fileSystemList) {
if (fileSystem.equalsIgnoreCase(fs.getSysTypeName())) {
return;
} else {
supportedFileSystems.add(fs.getSysTypeName());
}
}
throw new LocalStorageOperationException("File system type " + fileSystem
+ " is not supported for the current operating system. Supported File Systems are : "
+ StringUtils.join(supportedFileSystems, ","));
}
private static void executeCommandLine(final String commandLine, final long timeout)
throws LocalStorageOperationException, TimeoutException {
Executor executor = new DefaultExecutor();
executor.setExitValue(0);
ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
executor.setWatchdog(watchdog);
ProcessOutputStream outAndErr = new ProcessOutputStream();
try {
PumpStreamHandler streamHandler = new PumpStreamHandler(outAndErr);
executor.setStreamHandler(streamHandler);
logger.info("Executing commandLine : '" + commandLine + "'");
executor.execute(CommandLine.parse(commandLine));
logger.info("Execution completed successfully. Process output was : " + outAndErr.getOutput());
} catch (final Exception e) {
if (watchdog.killedProcess()) {
throw new TimeoutException("Timed out while executing commandLine : '" + commandLine + "'");
}
throw new LocalStorageOperationException("Failed executing commandLine : '" + commandLine
+ ". Process output was : " + outAndErr.getOutput(), e);
}
}
private static String executeSilentCommandLineReturnOutput(final String commandLine, final long timeout)
throws LocalStorageOperationException, TimeoutException {
Executor executor = new DefaultExecutor();
executor.setExitValue(0);
ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
executor.setWatchdog(watchdog);
ProcessOutputStream outAndErr = new ProcessOutputStream();
try {
PumpStreamHandler streamHandler = new PumpStreamHandler(outAndErr);
executor.setStreamHandler(streamHandler);
executor.execute(CommandLine.parse(commandLine));
} catch (final Exception e) {
if (watchdog.killedProcess()) {
throw new TimeoutException("Timed out while executing commandLine : '" + commandLine + "'");
}
throw new LocalStorageOperationException("Failed executing commandLine : '" + commandLine
+ ". Process output was : " + outAndErr.getOutput(), e);
}
return outAndErr.getOutput();
}
/**
* Logs process output to the logger.
* @author elip
*
*/
private static class ProcessOutputStream extends LogOutputStream {
private List<String> output = new java.util.LinkedList<String>();
private String getOutput() {
return StringUtils.join(output, "\n");
}
@Override
protected void processLine(final String line, final int level) {
output.add(line);
logger.info(line);
}
}
}