/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.storage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.BackupSnapshotAnswer;
import com.cloud.agent.api.BackupSnapshotCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.GetFileStatsAnswer;
import com.cloud.agent.api.GetFileStatsCommand;
import com.cloud.agent.api.GetStorageStatsAnswer;
import com.cloud.agent.api.GetStorageStatsCommand;
import com.cloud.agent.api.ManageSnapshotAnswer;
import com.cloud.agent.api.ManageSnapshotCommand;
import com.cloud.agent.api.ModifyStoragePoolCommand;
import com.cloud.agent.api.PingCommand;
import com.cloud.agent.api.PingStorageCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupStorageCommand;
import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer;
import com.cloud.agent.api.storage.CreatePrivateTemplateCommand;
import com.cloud.agent.api.storage.DestroyCommand;
import com.cloud.agent.api.storage.DownloadCommand;
import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
import com.cloud.agent.api.storage.ShareAnswer;
import com.cloud.agent.api.storage.ShareCommand;
import com.cloud.agent.api.storage.UpgradeDiskAnswer;
import com.cloud.agent.api.storage.UpgradeDiskCommand;
import com.cloud.host.Host;
import com.cloud.resource.ServerResource;
import com.cloud.resource.ServerResourceBase;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.template.DownloadManager;
import com.cloud.storage.template.TemplateInfo;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
/**
* StorageResource represents the storage server. It executes commands
* against the storage server.
*
* @config
* {@table
* || Param Name | Description | Values | Default ||
* || pool | name of the pool to use | String | tank ||
* || parent | parent path to all of the templates and the trashcan | Path | [pool]/vmops ||
* || scripts.dir | directory to the scripts | Path | ./scripts ||
* || scripts.timeout | timeout value to use when executing scripts | seconds | 120s ||
* || public.templates.root.dir | directory where public templates reside | Path | [pool]/volumes/demo/template/public/os ||
* || private.templates.root.dir | directory where private templates reside | Path | [pool]/volumes/template/demo/private ||
* || templates.download.dir | directory where templates are downloaded prior to being installed | Path | [pool]/volumes/demo/template/download ||
* || install.timeout.pergig | timeout for the template creation script per downloaded gigabyte | seconds | 900s ||
* || install.numthreads | number of concurrent install threads | number | 3 ||
* }
*/
public abstract class StorageResource extends ServerResourceBase implements ServerResource {
protected static final Logger s_logger = Logger.getLogger(StorageResource.class);
protected String _createvmPath;
protected String _listvmdiskPath;
protected String _listvmdisksizePath;
protected String _delvmPath;
protected String _manageSnapshotPath;
protected String _manageVolumePath;
protected String _createPrivateTemplatePath;
protected String _guid;
protected String _rootDir;
protected String _rootFolder = "vmops";
protected String _parent;
protected String _trashcanDir;
protected String _vmFolder;
protected String _trashcanFolder;
protected String _datadisksFolder;
protected String _datadisksDir;
protected String _sharePath;
protected String _infoPath;
protected int _timeout;
protected String _iqnPath;
protected String _checkchildrenPath;
protected String _userPrivateTemplateRootDir;
protected String _zfsScriptsDir;
protected DownloadManager _downloadManager;
protected Map<Long, VolumeSnapshotRequest> _volumeHourlySnapshotRequests = new HashMap<Long, VolumeSnapshotRequest>();
protected Map<Long, VolumeSnapshotRequest> _volumeDailySnapshotRequests = new HashMap<Long, VolumeSnapshotRequest>();
protected String _instance;
@Override
public Answer executeRequest(final Command cmd) {
if (cmd instanceof DestroyCommand) {
return execute((DestroyCommand)cmd);
} else if (cmd instanceof GetFileStatsCommand) {
return execute((GetFileStatsCommand)cmd);
} else if (cmd instanceof PrimaryStorageDownloadCommand) {
return execute((PrimaryStorageDownloadCommand)cmd);
} else if (cmd instanceof DownloadCommand) {
return execute((DownloadCommand)cmd);
} else if (cmd instanceof GetStorageStatsCommand) {
return execute((GetStorageStatsCommand)cmd);
} else if (cmd instanceof UpgradeDiskCommand) {
return execute((UpgradeDiskCommand) cmd);
} else if (cmd instanceof ShareCommand) {
return execute((ShareCommand)cmd);
} else if (cmd instanceof ManageSnapshotCommand) {
return execute((ManageSnapshotCommand)cmd);
} else if (cmd instanceof BackupSnapshotCommand) {
return execute((BackupSnapshotCommand)cmd);
} else if (cmd instanceof CreatePrivateTemplateCommand) {
return execute((CreatePrivateTemplateCommand)cmd);
} else if (cmd instanceof ModifyStoragePoolCommand ){
return execute ((ModifyStoragePoolCommand) cmd);
} else {
s_logger.warn("StorageResource: Unsupported command");
return Answer.createUnsupportedCommandAnswer(cmd);
}
}
protected Answer execute(ModifyStoragePoolCommand cmd) {
s_logger.warn("Unsupported: network file system mount attempted");
return Answer.createUnsupportedCommandAnswer(cmd);
}
protected ShareAnswer execute(final ShareCommand cmd) {
return new ShareAnswer(cmd, new HashMap<String, Integer>());
}
protected Answer execute(final PrimaryStorageDownloadCommand cmd) {
return Answer.createUnsupportedCommandAnswer(cmd);
}
protected Answer execute(final DownloadCommand cmd) {
return _downloadManager.handleDownloadCommand(cmd);
}
public String getSecondaryStorageMountPoint(String uri) {
return null;
}
protected String getUserPath(final String image) {
return image.substring(0, image.indexOf(File.separator, _parent.length() + 2)).intern();
}
protected Answer execute(final GetFileStatsCommand cmd) {
final String image = cmd.getPaths();
final Script command = new Script(_listvmdisksizePath, _timeout, s_logger);
command.add("-d", image);
command.add("-a");
final SizeParser parser = new SizeParser();
final String result = command.execute(parser);
if (result != null) {
return new Answer(cmd, false, result);
}
return new GetFileStatsAnswer(cmd, parser.size);
}
protected List<VolumeVO> getVolumes(final String rootdiskFolder, final String datadiskFolder, final String datadiskName) {
final ArrayList<VolumeVO> vols = new ArrayList<VolumeVO>();
// Get the rootdisk volume
String path = rootdiskFolder + File.separator + "rootdisk";
long totalSize = getVolumeSize(path);
VolumeVO vol = new VolumeVO(null, null, -1, -1, -1, -1, new Long(-1), rootdiskFolder, path, totalSize, Volume.VolumeType.ROOT);
vols.add(vol);
// Get the datadisk volume
if (datadiskFolder != null && datadiskName != null) {
path = datadiskFolder + File.separator + datadiskName;
totalSize = getVolumeSize(path);
vol = new VolumeVO(null, null, -1, -1, -1, -1, new Long(-1), datadiskFolder, path, totalSize, Volume.VolumeType.DATADISK);
vols.add(vol);
}
return vols;
}
protected List<VolumeVO> getVolumes(final String imagePath) {
final ArrayList<VolumeVO> vols = new ArrayList<VolumeVO>();
String path = getVolumeName(imagePath, null);
long totalSize = getVolumeSize(path);
VolumeVO vol = new VolumeVO(null, null, -1, -1, -1, -1, new Long(-1), null, path, totalSize, Volume.VolumeType.ROOT);
vols.add(vol);
path = getVolumeName(imagePath, (long)1);
if (path != null) {
totalSize = getVolumeSize(path);
vol = new VolumeVO(null, null, -1, -1, -1, -1, new Long(-1), null, path, totalSize, Volume.VolumeType.DATADISK);
vols.add(vol);
}
return vols;
}
protected long getVolumeSize(final String volume) {
final Script command = new Script(_listvmdisksizePath, _timeout, s_logger);
command.add("-d", volume);
command.add("-t");
final SizeParser parser = new SizeParser();
final String result = command.execute(parser);
if (result != null) {
throw new CloudRuntimeException(result);
}
return parser.size;
}
protected String getVolumeName(final String imagePath, final Long diskNum) {
final Script command = new Script(_listvmdiskPath, _timeout, s_logger);
command.add("-i", imagePath);
if (diskNum == null) {
command.add("-r");
} else {
command.add("-d", diskNum.toString());
}
final PathParser parser = new PathParser();
final String result = command.execute(parser);
if (result != null) {
throw new CloudRuntimeException("Can't get volume name due to " + result);
}
return parser.path;
}
protected long convertFilesystemSize(final String size) {
if (size == null) {
return -1;
}
long multiplier = 1;
if (size.endsWith("T")) {
multiplier = 1024l * 1024l * 1024l * 1024l;
} else if (size.endsWith("G")) {
multiplier = 1024l * 1024l * 1024l;
} else if (size.endsWith("M")) {
multiplier = 1024l * 1024l;
} else if (size.endsWith("K")){
multiplier = 1024l;
} else {
long num;
try {
num = Long.parseLong(size);
} catch (NumberFormatException e) {
s_logger.debug("Unknow size:" + size);
return 0;
}
return num;
}
return (long)(Double.parseDouble(size.substring(0, size.length() - 1)) * multiplier);
}
protected abstract void cleanUpEmptyParents(String imagePath);
protected abstract long getUsedSize() ;
protected abstract long getTotalSize();
protected abstract String destroy(final String imagePath) ;
protected abstract String createTrashDir(final String imagePath, final StringBuilder path) ;
public abstract boolean existPath(final String path);
public abstract String createPath(final String createPath) ;
protected abstract Answer execute(DestroyCommand cmd) ;
protected abstract UpgradeDiskAnswer execute(final UpgradeDiskCommand cmd);
protected abstract String delete(String imagePath, String extra);
protected abstract Volume.StorageResourceType getStorageResourceType();
protected abstract void configureFolders(String name, Map<String, Object> params) throws ConfigurationException ;
protected GetStorageStatsAnswer execute(final GetStorageStatsCommand cmd) {
final long size = getUsedSize();
return size != -1 ? new GetStorageStatsAnswer(cmd, 0, size) : new GetStorageStatsAnswer(cmd, "Unable to get storage stats");
}
protected ManageSnapshotAnswer execute(final ManageSnapshotCommand cmd) {
final Script command = new Script(_manageSnapshotPath, _timeout, s_logger);
String path = null;
if (cmd.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.DESTROY_SNAPSHOT)) {
path = cmd.getSnapshotPath();
} else if (cmd.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) {
path = cmd.getVolumePath();
}
command.add(cmd.getCommandSwitch(), path);
command.add("-n", cmd.getSnapshotName());
final String result = command.execute();
return new ManageSnapshotAnswer(cmd, cmd.getSnapshotId(),cmd.getVolumePath(), (result == null), result);
}
protected BackupSnapshotAnswer execute(final BackupSnapshotCommand cmd) {
// This is implemented only for XenServerResource
Answer answer = Answer.createUnsupportedCommandAnswer(cmd);
return new BackupSnapshotAnswer(cmd, false, answer.getDetails(), null);
}
protected CreatePrivateTemplateAnswer execute(final CreatePrivateTemplateCommand cmd) {
final Script command = new Script(_createPrivateTemplatePath, _timeout, s_logger);
String installDir = _userPrivateTemplateRootDir;
if (installDir.startsWith("/")) {
installDir = installDir.substring(1);
}
command.add("-p", cmd.getSnapshotPath());
command.add("-s", cmd.getTemplateName());
command.add("-d", installDir);
command.add("-u", cmd.getUserFolder());
String templateName = cmd.getTemplateName().replaceAll(" ", "_"); //hard to pass spaces to shell scripts
if (templateName.length() > 32) {
templateName = templateName.substring(0,31); //truncate
}
command.add("-n", templateName);
final String result = command.execute();
CreatePrivateTemplateAnswer answer = new CreatePrivateTemplateAnswer(cmd, (result == null), result, null, 0, null, null);
if (result == null) {
answer.setPath("/" + installDir + "/" + cmd.getUserFolder() + "/" + templateName);
}
return answer;
}
protected String create(final String rootdiskFolder, final int rootDiskSizeGB) {
final Script command = new Script(_createvmPath, _timeout, s_logger);
command.add("-i", rootdiskFolder);
command.add("-S", Integer.toString(rootDiskSizeGB));
return command.execute();
}
protected String create(final String templateFolder, final String rootdiskFolder, final String userPath, final String dataPath, String localPath) {
final Script command = new Script(_createvmPath, _timeout, s_logger);
command.add("-t", templateFolder);
command.add("-i", rootdiskFolder);
command.add("-u", userPath);
if (dataPath != null) {
command.add("-d", dataPath);
}
return command.execute();
}
protected String create(final String templateFolder, final String rootdiskFolder, final String userPath, final String datadiskFolder, final String datadiskName, final int datadiskSize, String localPath) {
final Script command = new Script(_createvmPath, _timeout, s_logger);
// for private templates, the script needs the snapshot name being used to create the VM
command.add("-t", templateFolder);
command.add("-i", rootdiskFolder);
command.add("-u", userPath);
if (datadiskSize != 0) {
command.add("-f", datadiskFolder);
command.add("-s", Integer.toString(datadiskSize));
command.add("-n", datadiskName);
}
return command.execute();
}
@Override
public PingCommand getCurrentStatus(final long id) {
return new PingStorageCommand(Host.Type.Storage, id, new HashMap<String, Boolean>());
}
@Override
public StartupCommand[] initialize() {
final StartupStorageCommand cmd = new StartupStorageCommand(_parent, StoragePoolType.NetworkFilesystem, getTotalSize(), new HashMap<String, TemplateInfo>());
cmd.setResourceType(getStorageResourceType());
cmd.setIqn(getIQN());
fillNetworkInformation(cmd);
return new StartupCommand [] {cmd};
}
protected String getIQN() {
final Script command = new Script(_iqnPath, 500, s_logger);
final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
final String result = command.execute(parser);
if (result != null) {
throw new CloudRuntimeException("Unable to get iqn: " + result);
}
return parser.getLine();
}
@Override
protected String findScript(String script) {
return Script.findScript(_zfsScriptsDir, script);
}
@Override
protected abstract String getDefaultScriptsDir();
@Override
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
if (!super.configure(name, params)) {
s_logger.warn("Base class was unable to configure");
return false;
}
_zfsScriptsDir = (String)params.get("zfs.scripts.dir");
if (_zfsScriptsDir == null) {
_zfsScriptsDir = getDefaultScriptsDir();
}
String value = (String)params.get("scripts.timeout");
_timeout = NumbersUtil.parseInt(value, 1440) * 1000;
_createvmPath = findScript("createvm.sh");
if (_createvmPath == null) {
throw new ConfigurationException("Unable to find the createvm.sh");
}
s_logger.info("createvm.sh found in " + _createvmPath);
_delvmPath = findScript("delvm.sh");
if (_delvmPath == null) {
throw new ConfigurationException("Unable to find the delvm.sh");
}
s_logger.info("delvm.sh found in " + _delvmPath);
_listvmdiskPath = findScript("listvmdisk.sh");
if (_listvmdiskPath == null) {
throw new ConfigurationException("Unable to find the listvmdisk.sh");
}
s_logger.info("listvmdisk.sh found in " + _listvmdiskPath);
_listvmdisksizePath = findScript("listvmdisksize.sh");
if (_listvmdisksizePath == null) {
throw new ConfigurationException("Unable to find the listvmdisksize.sh");
}
s_logger.info("listvmdisksize.sh found in " + _listvmdisksizePath);
_iqnPath = findScript("get_iqn.sh");
if (_iqnPath == null) {
throw new ConfigurationException("Unable to find get_iqn.sh");
}
s_logger.info("get_iqn.sh found in " + _iqnPath);
_manageSnapshotPath = findScript("managesnapshot.sh");
if (_manageSnapshotPath == null) {
throw new ConfigurationException("Unable to find the managesnapshot.sh");
}
s_logger.info("managesnapshot.sh found in " + _manageSnapshotPath);
_manageVolumePath = findScript("managevolume.sh");
if (_manageVolumePath == null) {
throw new ConfigurationException("Unable to find managevolume.sh");
}
s_logger.info("managevolume.sh found in " + _manageVolumePath);
_createPrivateTemplatePath = findScript("create_private_template.sh");
if (_createPrivateTemplatePath == null) {
throw new ConfigurationException("Unable to find the create_private_template.sh");
}
s_logger.info("create_private_template.sh found in " + _createPrivateTemplatePath);
_checkchildrenPath = findScript("checkchildren.sh");
if (_checkchildrenPath == null) {
throw new ConfigurationException("Unable to find the checkchildren.sh");
}
value = (String)params.get("developer");
boolean isDeveloper = Boolean.parseBoolean(value);
_instance = (String)params.get("instance");
/*
String guid = (String)params.get("guid");
if (!isDeveloper && guid == null) {
throw new ConfigurationException("Unable to find the guid");
}
_guid = guid;*/
/*
params.put("template.parent", _parent);
_downloadManager = new DownloadManagerImpl();
_downloadManager.configure("DownloadManager", params);*/
return true;
}
@Override
public Host.Type getType() {
return Host.Type.Storage;
}
protected boolean hasChildren(final String path) {
final Script script = new Script(_checkchildrenPath, _timeout, s_logger);
script.add(path);
return script.execute() != null; // not null means there's children.
}
public static class SizeParser extends OutputInterpreter {
long size = 0;
@Override
public String interpret(final BufferedReader reader) throws IOException {
String line = null;
final StringBuilder buff = new StringBuilder();
while ((line = reader.readLine()) != null) {
buff.append(line);
}
size = Long.parseLong(buff.toString());
return null;
}
}
public static class PathParser extends OutputInterpreter {
String path;
@Override
public String interpret(final BufferedReader reader) throws IOException {
String line = null;
final StringBuilder buff = new StringBuilder();
while ((line = reader.readLine()) != null) {
buff.append(line);
}
path = buff.toString();
if (path != null && path.length() == 0) {
path = null;
}
return null;
}
}
protected class VolumeSnapshotRequest {
private final long _volumeId;
private final String _snapshotPath;
public VolumeSnapshotRequest(long volumeId, String snapshotPath) {
_volumeId = volumeId;
_snapshotPath = snapshotPath;
}
public long getVolumeId() {
return _volumeId;
}
public String getSnapshotPath() {
return _snapshotPath;
}
}
}