/** * 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.File; import java.net.InetAddress; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; import com.cloud.agent.api.ModifyStoragePoolAnswer; import com.cloud.agent.api.ModifyStoragePoolCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupStorageCommand; import com.cloud.agent.api.StoragePoolInfo; 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.UpgradeDiskAnswer; import com.cloud.agent.api.storage.UpgradeDiskCommand; import com.cloud.agent.api.to.VolumeTO; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Volume.StorageResourceType; import com.cloud.storage.template.TemplateInfo; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NfsUtils; import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.Script; public abstract class FileSystemStorageResource extends StorageResource { protected static final Logger s_logger = Logger.getLogger(FileSystemStorageResource.class); protected String _templateRootDir; protected String _poolName; protected String _poolUuid; protected String _localStoragePath; @Override public boolean existPath(String path) { if (path == null) { return false; } final Script cmd = new Script("ls", _timeout, s_logger); cmd.add(File.separator + path); final String result = cmd.execute(); if (result == null) { return true; } if (result == Script.ERR_TIMEOUT) { throw new CloudRuntimeException("Script timed out"); } return !result.contains("No such file or directory"); } @Override public String createPath(final String createPath) { final Script cmd = new Script("mkdir", _timeout, s_logger); cmd.add("-p", File.separator + createPath); return cmd.execute(); } @Override protected void fillNetworkInformation(StartupCommand cmd) { super.fillNetworkInformation(cmd); cmd.setIqn(null); } @Override protected long getUsedSize() { return getUsedSize(_rootDir); } protected long getUsedSize(String poolPath) { poolPath = getPoolPath(poolPath); if (poolPath == null) { return 0; } Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("df -Ph " + poolPath + " | grep -v Used | awk '{print $3}' "); final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); if (command.execute(parser) != null) { return -1; } return convertFilesystemSize(parser.getLine()); } private String getPoolPath(String poolPath) { if (!existPath(poolPath)) { poolPath = File.separator + poolPath; if (!existPath(poolPath)) return null; } return poolPath; } protected long getTotalSize(String poolPath) { poolPath = getPoolPath(poolPath); if (poolPath == null) { return 0; } Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("df -Ph " + poolPath + " | grep -v Size | awk '{print $2}' "); final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); if (command.execute(parser) != null) { return -1; } return convertFilesystemSize(parser.getLine()); } @Override protected long getTotalSize() { return getTotalSize(_rootDir); } @Override protected String createTrashDir(String imagePath, StringBuilder path) { final int index = imagePath.lastIndexOf(File.separator) + File.separator.length(); path.append(_trashcanDir); path.append(imagePath.substring(_parent.length(), index)); path.append(Long.toHexString(System.currentTimeMillis())); final Script cmd = new Script("mkdir", _timeout, s_logger); cmd.add("-p", path.toString()); final String result = cmd.execute(); if (result != null) { return result; } path.append(File.separator).append(imagePath.substring(index)); return null; } @Override protected String destroy(String imagePath) { final StringBuilder trashPath = new StringBuilder(); String result = createTrashDir(imagePath, trashPath); if (result != null) { return result; } final Script cmd = new Script("mv", _timeout, s_logger); cmd.add(imagePath); cmd.add(trashPath.toString()); result = cmd.execute(); if (result != null) { return result; } s_logger.warn("Path " + imagePath + " has been moved to " + trashPath.toString()); cleanUpEmptyParents(imagePath); return null; } @Override protected void cleanUpEmptyParents(String imagePath) { imagePath = imagePath.substring(0, imagePath.lastIndexOf(File.separator)); String destroyPath = null; while (imagePath.length() > _parent.length() && !hasChildren(imagePath)) { destroyPath = imagePath; imagePath = imagePath.substring(0, imagePath.lastIndexOf(File.separator)); } if (destroyPath != null) { final Script cmd = new Script("rm", _timeout, s_logger); cmd.add("-rf", destroyPath); cmd.execute(); } } @Override protected Answer execute(DestroyCommand cmd) { VolumeTO volume = cmd.getVolume(); String result = null; result = delete(volume.getPath()); return new Answer(cmd, result == null, result); } private String delete(String image) { final Script cmd = new Script(_delvmPath, _timeout, s_logger); cmd.add("-i", image); final String result = cmd.execute(); if (result != null) { return result; } //cleanUpEmptyParents(image); return null; } @Override protected String delete(String imagePath, String extra) { return delete (imagePath); } protected boolean isSharedNetworkFileSystem(String path) { if (path.endsWith("/")) { path = path.substring(0, path.length()-1); } Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("mount -t nfs | grep nfs | awk '{print $3}' | grep -x " + path); OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); if (command.execute(parser) != null || parser.getLine() == null) { //not NFS client command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("grep " + _rootDir + " /etc/exports"); parser = new OutputInterpreter.OneLineParser(); if (command.execute(parser) == null && parser.getLine() != null) { return true; } } else if (parser.getLine() != null) { return true; } return false; } private List<String[]> getNfsMounts(String path) { if (path != null && path.endsWith("/")) { path = path.substring(0, path.length()-1); } Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); if (path != null) { command.add("cat /proc/mounts | grep nfs | grep " + path ); } else { command.add("cat /proc/mounts | grep nfs | grep -v rpc_pipefs "); } OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); if (command.execute(parser) != null || parser.getLines() == null) { //not NFS client return null; } else { List<String[]> result = new ArrayList<String[]>(); String[] lines = parser.getLines().split("\\n"); for (String line: lines){ String [] toks = line.split(" "); if ( toks.length < 4) { continue; } String [] hostpart = toks[0].split(":"); if (hostpart.length != 2) { continue; } String localPath = toks[1]; result.add(new String [] {hostpart[0], hostpart[1], localPath}); } return result; } } public List<String[]> getNetworkFileSystemServer(String path) { return getNfsMounts(path); } @Override protected String create( String templatePath, final String rootdiskFolder, final String userPath, final String datadiskFolder, final String datadiskName, final int datadiskSize, String localPath) { s_logger.debug("Creating volumes by cloning " + templatePath); final Script command = new Script(_createvmPath, _timeout, s_logger); command.add("-t", templatePath); 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 protected UpgradeDiskAnswer execute(UpgradeDiskCommand cmd) { // TODO Auto-generated method stub return null; } @Override public StartupCommand []initialize() { StartupCommand [] cmds = super.initialize(); StartupStorageCommand cmd = (StartupStorageCommand)cmds[0]; initLocalStorage(cmd); cmd.setTemplateInfo(new HashMap<String, TemplateInfo>()); //empty template info return new StartupCommand[] {cmd}; } @Override protected StorageResourceType getStorageResourceType() { return StorageResourceType.STORAGE_POOL; } protected String mountNfs(String hostAddress, String hostPath, String localPath) { final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("mount -t nfs -o acdirmax=0,acdirmin=0 " + hostAddress + ":" + hostPath + " " + localPath); String result = command.execute(parser); return result; } protected String umountNfs(String localPath) { Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("umount " + localPath); String result = command.execute(); return result; } protected void initLocalStorage(StartupStorageCommand cmd) { if (!existPath(_localStoragePath)) { createPath(_localStoragePath); } //setPoolPath(_localStoragePath); long capacity = getTotalSize(_localStoragePath); long used = getUsedSize(_localStoragePath); StoragePoolInfo poolInfo = new StoragePoolInfo( "Local Storage", "file://" + cmd.getPrivateIpAddress() + "/" + _localStoragePath, "localhost", _localStoragePath, _localStoragePath, StoragePoolType.Filesystem, capacity, capacity - used); cmd.setPoolInfo(poolInfo); } private Answer setFSStoragePool(ModifyStoragePoolCommand cmd) { StoragePoolVO pool = cmd.getPool(); String localPath = pool.getPath(); File localStorage = new File(localPath); if (!localStorage.exists()) { localStorage.mkdir(); } /*String result = setPoolPath(localPath); if (result != null) { return new Answer(cmd, false, " Failed to create folders"); }*/ if (_instance != null) { localPath = localPath + File.separator + _instance; } _poolName = pool.getName(); long capacity = getTotalSize(localPath); long used = getUsedSize(localPath); long available = capacity - used; Map<String, TemplateInfo> tInfo = new HashMap<String, TemplateInfo>(); ModifyStoragePoolAnswer answer = new ModifyStoragePoolAnswer(cmd, capacity, available, tInfo); return answer; } @Override protected Answer execute(ModifyStoragePoolCommand cmd) { StoragePoolVO pool = cmd.getPool(); if (pool.getPoolType() == StoragePoolType.Filesystem) { return setFSStoragePool(cmd); } if (cmd.getAdd()) { String result; String hostPath = pool.getPath(); String hostPath2 = pool.getPath(); if (hostPath.endsWith("/")) { hostPath2 = hostPath.substring(0, hostPath.length()-1); } String localPath = cmd.getLocalPath(); boolean alreadyMounted = false; List<String[]> shareInfo = getNfsMounts(null); if (shareInfo != null) { for (String [] share: shareInfo){ String host = share[0]; String path = share[1]; String path2 = path; if (path.endsWith("/")) { path2 = path.substring(0, path.length()-1); } if (!path.equals(hostPath) && !path2.equals(hostPath2)){ continue; } if (host.equalsIgnoreCase(pool.getHostAddress())){ alreadyMounted = true; localPath = share[2]; result = null; break; } else { try { InetAddress currAddr = InetAddress.getByName(host); InetAddress hostAddr = InetAddress.getByName(pool.getHostAddress()); if (currAddr.equals(hostAddr)){ alreadyMounted = true; result = null; localPath = share[2]; break; } } catch (UnknownHostException e) { continue; } } } } String localPath2 = localPath; if (localPath.endsWith("/")){ localPath2 = localPath.substring(0,localPath.length()-1); } if (!alreadyMounted){ Script mkdir = new Script("/bin/bash", _timeout, s_logger); mkdir.add("-c"); mkdir.add("mkdir -p " + localPath); final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); result = mkdir.execute(parser); if (result != null) { return new Answer(cmd, false, "Failed to create local path: " + result); } result = mountNfs(pool.getHostAddress(), pool.getPath(), localPath); if (result != null) { return new Answer(cmd, false, " Failed to mount: " + result); } } /*result = setPoolPath(localPath); if (result != null) { return new Answer(cmd, false, " Failed to create folders"); }*/ if (_instance != null) { localPath = localPath + File.separator + _instance; } _poolName =pool.getName(); _poolUuid = pool.getUuid(); long capacity = getTotalSize(localPath); long used = getUsedSize(localPath); long available = capacity - used; Map<String, TemplateInfo> tInfo = new HashMap<String, TemplateInfo>(); ModifyStoragePoolAnswer answer = new ModifyStoragePoolAnswer(cmd, capacity, available, tInfo); return answer; } else { Script command = new Script("/bin/bash", _timeout, s_logger); command.add("-c"); command.add("umount " + cmd.getLocalPath()); final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); String result = command.execute(parser); if (result != null) { return new Answer(cmd, false, " Failed to unmount: " + result); } return new Answer(cmd); } } @Override protected String create(String templateFolder, String rootdiskFolder, String userPath, String dataPath, String localPath) { s_logger.debug("Creating volumes"); final Script command = new Script(_createvmPath, _timeout, s_logger); command.add("-t", templateFolder); command.add("-i", rootdiskFolder); command.add("-u", userPath); return command.execute(); } protected String mountSecondaryStorage(String tmplMpt, String templatePath, String hostMpt) { String mountStr = null; try { mountStr = NfsUtils.url2Mount(tmplMpt); } catch (URISyntaxException e) { s_logger.debug("Is not a valid url" + tmplMpt); return null; } String []tok = mountStr.split(":"); /*Mount already?*/ if (!isNfsMounted(tok[0], tok[1], hostMpt)) { mountNfs(tok[0], tok[1], hostMpt); } if (!templatePath.startsWith("/")) templatePath = hostMpt + "/" + templatePath; else templatePath = hostMpt + templatePath; return templatePath; } protected boolean isNfsMounted(final String remoteHost, final String remotePath, final String mountPath) { boolean alreadyMounted = false; List<String[]> shareInfo = getNfsMounts(null); if (shareInfo != null) { for (String [] share: shareInfo){ String host = share[0]; String path = share[1]; String path2 = path; String localPath = share[2]; String localPath2 = localPath; if (path.endsWith("/")) { path2 = path.substring(0, path.length()-1); } if (localPath.endsWith("/")) { localPath2 = localPath.substring(0, localPath.length() -1); } if ((!path.equals(remotePath) && !path2.equals(remotePath)) || (!localPath.equals(mountPath) && !localPath2.equals(mountPath)) ){ continue; } if (host.equalsIgnoreCase(remoteHost)){ alreadyMounted = true; break; } else { try { InetAddress currAddr = InetAddress.getByName(host); InetAddress hostAddr = InetAddress.getByName(remoteHost); if (currAddr.equals(hostAddr)){ alreadyMounted = true; break; } } catch (UnknownHostException e) { continue; } } } } return alreadyMounted; } @Override protected String getDefaultScriptsDir() { // TODO Auto-generated method stub return null; } @Override protected CreatePrivateTemplateAnswer execute(CreatePrivateTemplateCommand cmd) { CreatePrivateTemplateAnswer answer = super.execute(cmd); answer.setPath(answer.getPath().replaceFirst(_rootDir, "")); answer.setPath(answer.getPath().replaceFirst("^/*", "/")); return answer; } }