// 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.cloudstack.storage.resource;

import static com.cloud.network.NetworkModel.METATDATA_DIR;
import static com.cloud.network.NetworkModel.PASSWORD_DIR;
import static com.cloud.network.NetworkModel.PASSWORD_FILE;
import static com.cloud.network.NetworkModel.PUBLIC_KEYS_FILE;
import static com.cloud.network.NetworkModel.USERDATA_DIR;
import static com.cloud.network.NetworkModel.USERDATA_FILE;
import static com.cloud.utils.storage.S3.S3Utils.putFile;
import static java.util.Arrays.asList;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.naming.ConfigurationException;

import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
import org.apache.cloudstack.storage.NfsMountManagerImpl.PathParser;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.DownloadCommand;
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
import org.apache.cloudstack.storage.command.MoveVolumeCommand;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
import org.apache.cloudstack.storage.command.UploadStatusAnswer;
import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
import org.apache.cloudstack.storage.command.UploadStatusCommand;
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
import org.apache.cloudstack.storage.configdrive.ConfigDrive;
import org.apache.cloudstack.storage.configdrive.ConfigDriveBuilder;
import org.apache.cloudstack.storage.formatinspector.Qcow2Inspector;
import org.apache.cloudstack.storage.template.DownloadManager;
import org.apache.cloudstack.storage.template.DownloadManagerImpl;
import org.apache.cloudstack.storage.template.UploadEntity;
import org.apache.cloudstack.storage.template.UploadManager;
import org.apache.cloudstack.storage.template.UploadManagerImpl;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.cloudstack.utils.security.DigestHelper;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.joda.time.DateTime;
import org.joda.time.format.ISODateTimeFormat;

import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckHealthAnswer;
import com.cloud.agent.api.CheckHealthCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.ComputeChecksumCommand;
import com.cloud.agent.api.DeleteSnapshotsDirCommand;
import com.cloud.agent.api.GetStorageStatsAnswer;
import com.cloud.agent.api.GetStorageStatsCommand;
import com.cloud.agent.api.HandleConfigDriveIsoAnswer;
import com.cloud.agent.api.HandleConfigDriveIsoCommand;
import com.cloud.agent.api.PingCommand;
import com.cloud.agent.api.PingStorageCommand;
import com.cloud.agent.api.ReadyAnswer;
import com.cloud.agent.api.ReadyCommand;
import com.cloud.agent.api.SecStorageFirewallCfgCommand;
import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig;
import com.cloud.agent.api.SecStorageSetupAnswer;
import com.cloud.agent.api.SecStorageSetupCommand;
import com.cloud.agent.api.SecStorageVMSetupCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupSecondaryStorageCommand;
import com.cloud.agent.api.storage.CreateDatadiskTemplateAnswer;
import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand;
import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand;
import com.cloud.agent.api.storage.DeleteEntityDownloadURLCommand;
import com.cloud.agent.api.storage.DownloadAnswer;
import com.cloud.agent.api.storage.GetDatadisksAnswer;
import com.cloud.agent.api.storage.GetDatadisksCommand;
import com.cloud.agent.api.storage.ListTemplateAnswer;
import com.cloud.agent.api.storage.ListTemplateCommand;
import com.cloud.agent.api.storage.ListVolumeAnswer;
import com.cloud.agent.api.storage.ListVolumeCommand;
import com.cloud.agent.api.storage.OVFHelper;
import com.cloud.agent.api.storage.UploadCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DatadiskTO;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.S3TO;
import com.cloud.agent.api.to.SwiftTO;
import com.cloud.configuration.Resource;
import com.cloud.exception.InternalErrorException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.Host;
import com.cloud.host.Host.Type;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.element.NetworkElement;
import com.cloud.resource.ServerResourceBase;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.StorageLayer;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.template.OVAProcessor;
import com.cloud.storage.template.Processor;
import com.cloud.storage.template.Processor.FormatInfo;
import com.cloud.storage.template.QCOW2Processor;
import com.cloud.storage.template.RawImageProcessor;
import com.cloud.storage.template.TARProcessor;
import com.cloud.storage.template.TemplateConstants;
import com.cloud.storage.template.TemplateLocation;
import com.cloud.storage.template.TemplateProp;
import com.cloud.storage.template.VhdProcessor;
import com.cloud.storage.template.VmdkProcessor;
import com.cloud.utils.EncryptionUtil;
import com.cloud.utils.LogUtils;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.SwiftUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.cloud.utils.storage.S3.S3Utils;
import com.cloud.vm.SecondaryStorageVm;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class NfsSecondaryStorageResource extends ServerResourceBase implements SecondaryStorageResource {

    protected Logger logger = LogManager.getLogger(NfsSecondaryStorageResource.class);

    private static final String TEMPLATE_ROOT_DIR = "template/tmpl";
    private static final String VOLUME_ROOT_DIR = "volumes";
    private static final String POST_UPLOAD_KEY_LOCATION = "/etc/cloudstack/agent/ms-psk";
    private static final String ORIGINAL_FILE_EXTENSION = ".orig";

    private static final String USE_HTTPS_TO_UPLOAD = "useHttpsToUpload";

    private static final Map<String, String> updatableConfigData = Maps.newHashMap();
    static {

        updatableConfigData.put(PUBLIC_KEYS_FILE, METATDATA_DIR);
        updatableConfigData.put(USERDATA_FILE, USERDATA_DIR);
        updatableConfigData.put(PASSWORD_FILE, PASSWORD_DIR);
    }

    int _timeout;

    public int getTimeout() {
        return _timeout;
    }

    public void setTimeout(int timeout) {
        _timeout = timeout;
    }

    String _instance;
    String _dc;
    String _pod;
    String _guid;
    String _role;
    Map<String, Object> _params;
    protected StorageLayer _storage;
    protected boolean _inSystemVM = false;
    boolean _sslCopy = false;

    protected DownloadManager _dlMgr;
    protected UploadManager _upldMgr;
    private String _configSslScr;
    private String _configAuthScr;
    private String _configIpFirewallScr;
    private String _publicIp;
    private String _hostname;
    private String _localgw;
    private String _eth1mask;
    private String _eth1ip;
    private String _storageIp;
    private String _storageNetmask;
    private String _storageGateway;
    private String _nfsVersion;
    private final List<String> nfsIps = new ArrayList<>();
    protected String _parent = "/mnt/SecStorage";
    final private String _tmpltpp = "template.properties";
    protected String createTemplateFromSnapshotXenScript;
    private HashMap<String, UploadEntity> uploadEntityStateMap = new HashMap<>();
    private String _ssvmPSK = null;
    private long processTimeout;

    public void setParentPath(String path) {
        _parent = path;
    }

    public String getMountingRoot() {
        return _parent;
    }

    @Override
    public void disconnected() {
    }

    public void setInSystemVM(boolean inSystemVM) {
        _inSystemVM = inSystemVM;
    }

    /**
     * Retrieve converted "nfsVersion" value from params
     * @param params
     * @return nfsVersion value if exists, null in other case
     */
    public static String retrieveNfsVersionFromParams(Map<String, Object> params) {
        return (String)params.get("nfsVersion");
    }

    @Override
    public Answer executeRequest(Command cmd) {
        logger.debug(LogUtils.logGsonWithoutException("Executing command %s [%s].", cmd.getClass().getSimpleName(), cmd));
        if (cmd instanceof DownloadProgressCommand) {
            return _dlMgr.handleDownloadCommand(this, (DownloadProgressCommand)cmd);
        } else if (cmd instanceof DownloadCommand) {
            return execute((DownloadCommand)cmd);
        } else if (cmd instanceof UploadCommand) {
            return _upldMgr.handleUploadCommand(this, (UploadCommand)cmd);
        } else if (cmd instanceof CreateEntityDownloadURLCommand) {
            return _upldMgr.handleCreateEntityURLCommand((CreateEntityDownloadURLCommand)cmd);
        } else if (cmd instanceof DeleteEntityDownloadURLCommand) {
            return _upldMgr.handleDeleteEntityDownloadURLCommand((DeleteEntityDownloadURLCommand)cmd);
        } else if (cmd instanceof GetStorageStatsCommand) {
            return execute((GetStorageStatsCommand)cmd);
        } else if (cmd instanceof CheckHealthCommand) {
            return new CheckHealthAnswer((CheckHealthCommand)cmd, true);
        } else if (cmd instanceof ReadyCommand) {
            return new ReadyAnswer((ReadyCommand)cmd);
        } else if (cmd instanceof SecStorageFirewallCfgCommand) {
            return execute((SecStorageFirewallCfgCommand)cmd);
        } else if (cmd instanceof SecStorageVMSetupCommand) {
            return execute((SecStorageVMSetupCommand)cmd);
        } else if (cmd instanceof SecStorageSetupCommand) {
            return execute((SecStorageSetupCommand)cmd);
        } else if (cmd instanceof ComputeChecksumCommand) {
            return execute((ComputeChecksumCommand)cmd);
        } else if (cmd instanceof ListTemplateCommand) {
            return execute((ListTemplateCommand)cmd);
        } else if (cmd instanceof ListVolumeCommand) {
            return execute((ListVolumeCommand)cmd);
        } else if (cmd instanceof DeleteSnapshotsDirCommand) {
            return execute((DeleteSnapshotsDirCommand)cmd);
        } else if (cmd instanceof CopyCommand) {
            return execute((CopyCommand)cmd);
        } else if (cmd instanceof DeleteCommand) {
            return execute((DeleteCommand)cmd);
        } else if (cmd instanceof UploadStatusCommand) {
            return execute((UploadStatusCommand)cmd);
        } else if (cmd instanceof HandleConfigDriveIsoCommand) {
            return execute((HandleConfigDriveIsoCommand)cmd);
        } else if (cmd instanceof GetDatadisksCommand) {
            return execute((GetDatadisksCommand)cmd);
        } else if (cmd instanceof CreateDatadiskTemplateCommand) {
            return execute((CreateDatadiskTemplateCommand)cmd);
        } else if (cmd instanceof MoveVolumeCommand) {
            return execute((MoveVolumeCommand)cmd);
        } else if (cmd instanceof ListDataStoreObjectsCommand) {
            return execute((ListDataStoreObjectsCommand)cmd);
        } else if (cmd instanceof QuerySnapshotZoneCopyCommand) {
            return execute((QuerySnapshotZoneCopyCommand)cmd);
        } else {
            return Answer.createUnsupportedCommandAnswer(cmd);
        }
    }

    private Answer execute(ListDataStoreObjectsCommand cmd) {
        return listFilesAtPath(getRootDir(cmd.getStore().getUrl(), _nfsVersion), cmd.getPath(), cmd.getStartIndex(), cmd.getPageSize());
    }

    private Answer execute(HandleConfigDriveIsoCommand cmd) {
        if (cmd.isCreate()) {
            if (cmd.getIsoData() == null) {
                return new HandleConfigDriveIsoAnswer(cmd, "Invalid config drive ISO data");
            }
            String nfsMountPoint = getRootDir(cmd.getDestStore().getUrl(), _nfsVersion);
            File isoFile = new File(nfsMountPoint, cmd.getIsoFile());
            if(isoFile.exists()) {
                logger.debug("config drive iso already exists");
            }
            Path tempDir = null;
            try {
                tempDir = java.nio.file.Files.createTempDirectory(ConfigDrive.CONFIGDRIVEDIR);
                File tmpIsoFile = ConfigDriveBuilder.base64StringToFile(cmd.getIsoData(), tempDir.toAbsolutePath().toString(), cmd.getIsoFile());
                copyLocalToNfs(tmpIsoFile, new File(cmd.getIsoFile()), cmd.getDestStore());
            } catch (IOException | ConfigurationException e) {
                return new HandleConfigDriveIsoAnswer(cmd, "Failed due to exception: " + e.getMessage());
            } finally {
                try {
                    if (tempDir != null) {
                        FileUtils.deleteDirectory(tempDir.toFile());
                    }
                } catch (IOException ioe) {
                    logger.warn("Failed to delete ConfigDrive temporary directory: " + tempDir.toString(), ioe);
                }
            }
            return new HandleConfigDriveIsoAnswer(cmd, NetworkElement.Location.SECONDARY, "Successfully saved config drive at secondary storage");
        } else {
            DataStoreTO dstore = cmd.getDestStore();
            if (dstore instanceof NfsTO) {
                NfsTO nfs = (NfsTO) dstore;
                String relativeTemplatePath = new File(cmd.getIsoFile()).getPath();
                String nfsMountPoint = getRootDir(nfs.getUrl(), _nfsVersion);
                File tmpltPath = new File(nfsMountPoint, relativeTemplatePath);
                try {
                    Files.deleteIfExists(tmpltPath.toPath());
                } catch (IOException e) {
                    return new HandleConfigDriveIsoAnswer(cmd, e);
                }
                return new HandleConfigDriveIsoAnswer(cmd);
            } else {
                return new HandleConfigDriveIsoAnswer(cmd, "Not implemented yet");
            }
        }
    }

    protected void copyLocalToNfs(File localFile, File isoFile, DataStoreTO destData) throws ConfigurationException, IOException {
        String scriptsDir = "scripts/storage/secondary";
        String createVolScr = Script.findScript(scriptsDir, "createvolume.sh");
        if (createVolScr == null) {
            throw new ConfigurationException("Unable to find createvolume.sh");
        }
        logger.info("createvolume.sh found in " + createVolScr);

        int installTimeoutPerGig = 180 * 60 * 1000;
        int imgSizeGigs = (int) Math.ceil(localFile.length() * 1.0d / (1024 * 1024 * 1024));
        imgSizeGigs++; // add one just in case
        long timeout = imgSizeGigs * installTimeoutPerGig;

        Script scr = new Script(createVolScr, timeout, logger);
        scr.add("-s", Integer.toString(imgSizeGigs));
        scr.add("-n", isoFile.getName());
        scr.add("-t", getRootDir(destData.getUrl(), _nfsVersion) + "/" + isoFile.getParent());
        scr.add("-f", localFile.getAbsolutePath());
        scr.add("-d", "configDrive");
        String result;
        result = scr.execute();

        if (result != null) {
            // script execution failure
            throw new CloudRuntimeException("Failed to run script " + createVolScr);
        }
    }

    public Answer execute(GetDatadisksCommand cmd) {
        DataTO srcData = cmd.getData();
        String configurationId = cmd.getConfigurationId();
        TemplateObjectTO template = (TemplateObjectTO)srcData;
        DataStoreTO srcStore = srcData.getDataStore();
        if (!(srcStore instanceof NfsTO)) {
            return new CreateDatadiskTemplateAnswer("Unsupported protocol");
        }
        NfsTO nfsImageStore = (NfsTO)srcStore;
        String secondaryStorageUrl = nfsImageStore.getUrl();
        assert (secondaryStorageUrl != null);

        String templateUrl = secondaryStorageUrl + File.separator + srcData.getPath();
        String templateDetails = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(template, "uuid", "path", "name");
        logger.debug(String.format("Trying to get disks of template [%s], using path [%s].", templateDetails, templateUrl));

        Pair<String, String> templateInfo = decodeTemplateRelativePathAndNameFromUrl(secondaryStorageUrl, templateUrl, template.getName());
        String templateRelativeFolderPath = templateInfo.first();

        try {
            String secondaryMountPoint = getRootDir(secondaryStorageUrl, _nfsVersion);
            logger.info(String.format("Trying to find template [%s] in secondary storage root mount point [%s].", templateDetails, secondaryMountPoint));

            String srcOVAFileName = getTemplateOnSecStorageFilePath(secondaryMountPoint, templateRelativeFolderPath, templateInfo.second(), ImageFormat.OVA.getFileExtension());

            String ovfFilePath = getOVFFilePath(srcOVAFileName);
            if (ovfFilePath == null) {
                Script command = new Script("tar", 0, logger);
                command.add("--no-same-owner");
                command.add("--no-same-permissions");
                command.add("-xf", srcOVAFileName);
                command.setWorkDir(secondaryMountPoint + File.separator + templateRelativeFolderPath);

                logger.info(String.format("Trying to decompress OVA file [%s] using command [%s].", srcOVAFileName, command.toString()));
                String result = command.execute();
                if (result != null) {
                    String msg = String.format("Unable to unpack snapshot OVA file [%s] due to [%s].", srcOVAFileName, result);
                    logger.error(msg);
                    throw new Exception(msg);
                }

                String directory = secondaryMountPoint + File.separator + templateRelativeFolderPath;
                command = new Script("chmod", 0, logger);
                command.add("-R");
                command.add("666", directory);

                logger.debug(String.format("Trying to add, recursivelly, permission 666 to directory [%s] using command [%s].", directory, command.toString()));
                result = command.execute();
                if (result != null) {
                    logger.warn(String.format("Unable to set permissions 666 for directory [%s] due to [%s].", directory, result));
                }
            }

            Script command = new Script("cp", _timeout, logger);
            command.add(ovfFilePath);
            command.add(ovfFilePath + ORIGINAL_FILE_EXTENSION);
            logger.debug(String.format("Trying to copy file from [%s] to [%s] using command [%s].", ovfFilePath, ovfFilePath + ORIGINAL_FILE_EXTENSION, command.toString()));
            String result = command.execute();
            if (result != null) {
                String msg = String.format("Unable to copy original OVF file [%s] to [%s] due to [%s].", ovfFilePath, ovfFilePath + ORIGINAL_FILE_EXTENSION, result);
                logger.error(msg);
            }

            logger.debug(String.format("Reading OVF file [%s] to retrive the number of disks present in OVA file.", ovfFilePath));
            OVFHelper ovfHelper = new OVFHelper();

            List<DatadiskTO> disks = ovfHelper.getOVFVolumeInfoFromFile(ovfFilePath, configurationId);
            logger.debug(LogUtils.logGsonWithoutException("Found %s disks reading OVF file [%s] and using configuration id [%s]. The disks specifications are [%s].",
                    disks.size(), ovfFilePath, configurationId, disks));
            return new GetDatadisksAnswer(disks);
        } catch (Exception e) {
            String msg = String.format("Failed to get disks from template [%s] due to [%s].", templateDetails, e.getMessage());
            logger.error(msg, e);
            return new GetDatadisksAnswer(msg);
        }
    }

    public Answer execute(CreateDatadiskTemplateCommand cmd) {
        TemplateObjectTO diskTemplate = new TemplateObjectTO();
        TemplateObjectTO dataDiskTemplate = (TemplateObjectTO)cmd.getDataDiskTemplate();
        DataStoreTO dataStore = dataDiskTemplate.getDataStore();
        if (!(dataStore instanceof NfsTO)) {
            return new CreateDatadiskTemplateAnswer("Unsupported protocol");
        }
        NfsTO nfsImageStore = (NfsTO)dataStore;
        String secondaryStorageUrl = nfsImageStore.getUrl();
        assert (secondaryStorageUrl != null);

        try {
            String secondaryMountPoint = getRootDir(secondaryStorageUrl, _nfsVersion);

            long templateId = dataDiskTemplate.getId();
            String templateUniqueName = dataDiskTemplate.getUniqueName();
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("no cmd? %s", cmd.stringRepresentation()));
            }
            String origDisk = cmd.getPath();
            long virtualSize = dataDiskTemplate.getSize();
            String diskName = origDisk.substring((origDisk.lastIndexOf(File.separator)) + 1);
            long physicalSize = new File(origDisk).length();
            String newTmplDir = getTemplateRelativeDirInSecStorage(dataDiskTemplate.getAccountId(), dataDiskTemplate.getId());
            String newTmplDirAbsolute = secondaryMountPoint + File.separator + newTmplDir;

            String ovfFilePath = getOVFFilePath(origDisk);
            if (!cmd.getBootable()) {
                // Create folder to hold datadisk template
                synchronized (newTmplDir.intern()) {
                    Script command = new Script("mkdir", _timeout, logger);
                    command.add("-p");
                    command.add(newTmplDirAbsolute);
                    String result = command.execute();
                    if (result != null) {
                        String msg = "Unable to prepare template directory: " + newTmplDir + ", storage: " + secondaryStorageUrl + ", error msg: " + result;
                        logger.error(msg);
                        throw new Exception(msg);
                    }
                }
                // Move Datadisk VMDK from parent template folder to Datadisk template folder
                synchronized (origDisk.intern()) {
                    Script command = new Script("mv", _timeout, logger);
                    command.add(origDisk);
                    command.add(newTmplDirAbsolute);
                    String result = command.execute();
                    if (result != null) {
                        String msg = "Unable to copy VMDK from parent template folder to datadisk template folder" + ", error msg: " + result;
                        logger.error(msg);
                        throw new Exception(msg);
                    }
                    command = new Script("cp", _timeout, logger);
                    command.add(ovfFilePath + ORIGINAL_FILE_EXTENSION);
                    command.add(newTmplDirAbsolute);
                    result = command.execute();
                    if (result != null) {
                        String msg = "Unable to copy VMDK from parent template folder to datadisk template folder" + ", error msg: " + result;
                        logger.error(msg);
                        throw new Exception(msg);
                    }
                }
            }

            // Create OVF for the disk
            String newOvfFilePath = newTmplDirAbsolute + File.separator + ovfFilePath.substring(ovfFilePath.lastIndexOf(File.separator) + 1);
            OVFHelper ovfHelper = new OVFHelper();
            ovfHelper.rewriteOVFFileForSingleDisk(ovfFilePath + ORIGINAL_FILE_EXTENSION, newOvfFilePath, diskName);

            postCreatePrivateTemplate(newTmplDirAbsolute, templateId, templateUniqueName, physicalSize, virtualSize);
            writeMetaOvaForTemplate(newTmplDirAbsolute, ovfFilePath.substring(ovfFilePath.lastIndexOf(File.separator) + 1), diskName, templateUniqueName, physicalSize);

            diskTemplate.setId(templateId);
            if (diskName.endsWith("iso")){
                diskTemplate.setPath(newTmplDir + File.separator + diskName);
            }
            else {
                diskTemplate.setPath(newTmplDir + File.separator + templateUniqueName + ".ova");
            }
            diskTemplate.setSize(virtualSize);
            diskTemplate.setPhysicalSize(physicalSize);
        } catch (Exception e) {
            String msg = "Create Datadisk template failed due to " + e.getMessage();
            logger.error(msg, e);
            return new CreateDatadiskTemplateAnswer(msg);
        }
        return new CreateDatadiskTemplateAnswer(diskTemplate);
    }

    public Answer execute(MoveVolumeCommand cmd) {
        String volumeToString = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "volumeUuid", "volumeName");

        String rootDir = getRootDir(cmd.getDatastoreUri(), _nfsVersion);

        if (!rootDir.endsWith("/")) {
            rootDir += "/";
        }

        Path srcPath = Paths.get(rootDir + cmd.getSrcPath());
        Path destPath = Paths.get(rootDir + cmd.getDestPath());

        try {
            logger.debug(String.format("Trying to create missing directories (if any) to move volume [%s].", volumeToString));
            Files.createDirectories(destPath.getParent());
            logger.debug(String.format("Trying to move volume [%s] to [%s].", volumeToString, destPath));
            Files.move(srcPath, destPath);

            String msg = String.format("Moved volume [%s] from [%s] to [%s].", volumeToString, srcPath, destPath);
            logger.debug(msg);

            return new Answer(cmd, true, msg);

        } catch (IOException ioException) {
            logger.error(String.format("Failed to move volume [%s] from [%s] to [%s] due to [%s].", volumeToString, srcPath, destPath, ioException.getMessage()),
                    ioException);
            return new Answer(cmd, ioException);
        }
    }

    /*
     *  return Pair of <Template relative path, Template name>
     *  Template url may or may not end with .ova extension
     */
    public Pair<String, String> decodeTemplateRelativePathAndNameFromUrl(String storeUrl, String templateUrl, String defaultName) {
        logger.debug(String.format("Trying to get template relative path and name from URL [%s].", templateUrl));
        String templateName = null;
        String mountPoint = null;
        if (templateUrl.endsWith(".ova")) {
            int index = templateUrl.lastIndexOf("/");
            mountPoint = templateUrl.substring(0, index);
            mountPoint = mountPoint.substring(storeUrl.length() + 1);
            if (!mountPoint.endsWith("/")) {
                mountPoint = mountPoint + "/";
            }

            templateName = templateUrl.substring(index + 1).replace(".ova", "");

            if (templateName == null || templateName.isEmpty()) {
                logger.debug(String.format("Cannot find template name from URL [%s]. Using default name [%s].", templateUrl, defaultName));
                templateName = defaultName;
            }
        } else {
            mountPoint = templateUrl.substring(storeUrl.length() + 1);
            if (!mountPoint.endsWith("/")) {
                mountPoint = mountPoint + "/";
            }
            templateName = defaultName;
        }

        logger.debug(String.format("Template relative path [%s] and name [%s] found from URL [%s].", mountPoint, templateName, templateUrl));
        return new Pair<String, String>(mountPoint, templateName);
    }

    public String getTemplateOnSecStorageFilePath(String secStorageMountPoint, String templateRelativeFolderPath, String templateName, String fileExtension) {
        logger.debug(String.format("Trying to find template [%s] with file extension [%s] in secondary storage mount point [%s] using relative folder path [%s].",
                templateName, fileExtension, secStorageMountPoint, templateRelativeFolderPath));
        StringBuffer sb = new StringBuffer();
        sb.append(secStorageMountPoint);
        if (!secStorageMountPoint.endsWith("/")) {
            sb.append("/");
        }

        sb.append(templateRelativeFolderPath);
        if (!secStorageMountPoint.endsWith("/")) {
            sb.append("/");
        }

        sb.append(templateName);
        if (!fileExtension.startsWith(".")) {
            sb.append(".");
        }
        sb.append(fileExtension);

        return sb.toString();
    }

    public static String getSecondaryDatastoreUUID(String storeUrl) {
        return UUID.nameUUIDFromBytes(storeUrl.getBytes()).toString();
    }

    private static String getTemplateRelativeDirInSecStorage(long accountId, long templateId) {
        return "template/tmpl/" + accountId + "/" + templateId;
    }

    private void postCreatePrivateTemplate(final String installFullPath, final long templateId, final String templateName, final long size, final long virtualSize) throws Exception {
        // TODO a bit ugly here
        try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(installFullPath + "/template.properties"), "UTF-8"));) {
            out.write("filename=" + templateName + ".ova");
            out.newLine();
            out.write("description=privateTemplate");
            out.newLine();
            out.write("hvm=false");
            out.newLine();
            out.write("size=" + size);
            out.newLine();
            out.write("ova=false");
            out.newLine();
            out.write("id=" + templateId);
            out.newLine();
            out.write("ova.filename=" + templateName + ".ova");
            out.newLine();
            out.write("uniquename=" + templateName);
            out.newLine();
            out.write("ova.virtualsize=" + virtualSize);
            out.newLine();
            out.write("virtualsize=" + virtualSize);
            out.newLine();
            out.write("ova.size=" + size);
            out.newLine();
            out.write("checksum=");
            out.newLine();
            out.write("public=false");
            out.newLine();
        }
    }

    private void writeMetaOvaForTemplate(final String installFullPath, final String ovfFilename, final String vmdkFilename, final String templateName, final long diskSize) throws Exception {

        // TODO a bit ugly here
        BufferedWriter out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(installFullPath + "/" + templateName + ".ova.meta"), "UTF-8"));
            out.write("ova.filename=" + templateName + ".ova");
            out.newLine();
            out.write("version=1.0");
            out.newLine();
            out.write("ovf=" + ovfFilename);
            out.newLine();
            out.write("numDisks=1");
            out.newLine();
            out.write("disk1.name=" + vmdkFilename);
            out.newLine();
            out.write("disk1.size=" + diskSize);
            out.newLine();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    private String getOVFFilePath(String srcOVAFileName) {
        logger.debug(String.format("Trying to get OVF file from OVA path [%s].", srcOVAFileName));

        File file = new File(srcOVAFileName);
        assert (_storage != null);
        String[] files = _storage.listFiles(file.getParent());

        if (files == null) {
            logger.warn(String.format("Cannot find any files in parent directory [%s] of OVA file [%s].", file.getParent(), srcOVAFileName));
            return null;
        }

        logger.debug(String.format("Found [%s] files in parent directory of OVA file [%s]. Files found are [%s].", files.length + 1, file.getParent(), StringUtils.join(files, ", ")));
        for (String fileName : files) {
            if (fileName.toLowerCase().endsWith(".ovf")) {
                File ovfFile = new File(fileName);
                String ovfFilePath = file.getParent() + File.separator + ovfFile.getName();
                logger.debug(String.format("Found OVF file [%s] from OVA file [%s].", ovfFilePath, srcOVAFileName));
                return ovfFilePath;
            }
        }
        logger.warn(String.format("Cannot find any OVF file in parent directory [%s] of OVA file [%s].", file.getParent(), srcOVAFileName));
        return null;
    }

    protected CopyCmdAnswer postProcessing(File destFile, String downloadPath, String destPath, DataTO srcData, DataTO destData) throws ConfigurationException {
        if (destData.getObjectType() == DataObjectType.SNAPSHOT) {
            SnapshotObjectTO snapshot = new SnapshotObjectTO();
            snapshot.setPath(destPath + File.separator + destFile.getName());

            CopyCmdAnswer answer = new CopyCmdAnswer(snapshot);
            return answer;
        }
        // do post processing to unzip the file if it is compressed
        String scriptsDir = "scripts/storage/secondary";
        String createTmpltScr = Script.findScript(scriptsDir, "createtmplt.sh");
        if (createTmpltScr == null) {
            throw new ConfigurationException("Unable to find createtmplt.sh");
        }
        logger.info("createtmplt.sh found in " + createTmpltScr);
        String createVolScr = Script.findScript(scriptsDir, "createvolume.sh");
        if (createVolScr == null) {
            throw new ConfigurationException("Unable to find createvolume.sh");
        }
        logger.info("createvolume.sh found in " + createVolScr);
        String script = srcData.getObjectType() == DataObjectType.TEMPLATE ? createTmpltScr : createVolScr;

        int installTimeoutPerGig = 180 * 60 * 1000;
        long imgSizeGigs = (long)Math.ceil(destFile.length() * 1.0d / (1024 * 1024 * 1024));
        imgSizeGigs++; // add one just in case
        long timeout = imgSizeGigs * installTimeoutPerGig;

        String origPath = destFile.getAbsolutePath();
        String extension = null;
        if (srcData.getObjectType() == DataObjectType.TEMPLATE) {
            extension = ((TemplateObjectTO)srcData).getFormat().getFileExtension();
        } else if (srcData.getObjectType() == DataObjectType.VOLUME) {
            extension = ((VolumeObjectTO)srcData).getFormat().getFileExtension();
        }

        String templateName = UUID.randomUUID().toString();
        String templateFilename = templateName + "." + extension;
        Script scr = new Script(script, timeout, logger);
        scr.add("-s", Long.toString(imgSizeGigs)); // not used for now
        scr.add("-n", templateFilename);

        scr.add("-t", downloadPath);
        scr.add("-f", origPath); // this is the temporary
        // template file downloaded
        String result;
        result = scr.execute();

        if (result != null) {
            // script execution failure
            throw new CloudRuntimeException("Failed to run script " + script);
        }

        String finalFileName = templateFilename;
        String finalDownloadPath = destPath + File.separator + templateFilename;
        // compute the size of
        long size = _storage.getSize(downloadPath + File.separator + templateFilename);

        DataTO newDestTO = null;

        if (destData.getObjectType() == DataObjectType.TEMPLATE) {
            TemplateObjectTO newTemplTO = new TemplateObjectTO();
            newTemplTO.setPath(finalDownloadPath);
            newTemplTO.setName(finalFileName);
            newTemplTO.setSize(size);
            newTemplTO.setPhysicalSize(size);
            newDestTO = newTemplTO;
        } else {
            VolumeObjectTO newVolTO = new VolumeObjectTO();
            newVolTO.setPath(finalDownloadPath);
            newVolTO.setName(finalFileName);
            newVolTO.setSize(size);
            newDestTO = newVolTO;
        }

        return new CopyCmdAnswer(newDestTO);
    }

    protected Answer copyFromSwiftToNfs(CopyCommand cmd, DataTO srcData, SwiftTO swiftTO, DataTO destData, NfsTO destImageStore) {
        final String storagePath = destImageStore.getUrl();
        final String destPath = destData.getPath();
        try {
            String downloadPath = determineStorageTemplatePath(storagePath, destPath, _nfsVersion);
            final File downloadDirectory = _storage.getFile(downloadPath);

            if (downloadDirectory.exists()) {
                logger.debug("Directory " + downloadPath + " already exists");
            } else {
                if (!downloadDirectory.mkdirs()) {
                    final String errMsg = "Unable to create directory " + downloadPath + " to copy from Swift to cache.";
                    logger.error(errMsg);
                    return new CopyCmdAnswer(errMsg);
                }
            }

            File destFile = SwiftUtil.getObject(swiftTO, downloadDirectory, srcData.getPath());
            return postProcessing(destFile, downloadPath, destPath, srcData, destData);
        } catch (Exception e) {
            logger.debug("Failed to copy swift to nfs", e);
            return new CopyCmdAnswer(e.toString());
        }
    }

    protected Answer copyFromS3ToNfs(CopyCommand cmd, DataTO srcData, S3TO s3, DataTO destData, NfsTO destImageStore) {
        final String storagePath = destImageStore.getUrl();
        final String destPath = destData.getPath();

        try {

            String downloadPath = determineStorageTemplatePath(storagePath, destPath, _nfsVersion);
            final File downloadDirectory = _storage.getFile(downloadPath);

            if (downloadDirectory.exists()) {
                logger.debug("Directory " + downloadPath + " already exists");
            } else {
                if (!downloadDirectory.mkdirs()) {
                    final String errMsg = "Unable to create directory " + downloadPath + " to copy from S3 to cache.";
                    logger.error(errMsg);
                    return new CopyCmdAnswer(errMsg);
                }
            }
            File destFile = new File(downloadDirectory, StringUtils.substringAfterLast(srcData.getPath(), S3Utils.SEPARATOR));
            S3Utils.getFile(s3, s3.getBucketName(), srcData.getPath(), destFile).waitForCompletion();

            return postProcessing(destFile, downloadPath, destPath, srcData, destData);
        } catch (Exception e) {

            final String errMsg = String.format("Failed to download" + "due to $1%s", e.getMessage());
            logger.error(errMsg, e);
            return new CopyCmdAnswer(errMsg);
        }
    }

    protected Answer copySnapshotToTemplateFromNfsToNfsXenserver(CopyCommand cmd, SnapshotObjectTO srcData, NfsTO srcDataStore, TemplateObjectTO destData, NfsTO destDataStore) {
        String srcMountPoint = getRootDir(srcDataStore.getUrl(), _nfsVersion);
        String snapshotPath = srcData.getPath();
        int index = snapshotPath.lastIndexOf("/");
        String snapshotName = snapshotPath.substring(index + 1);
        if (!snapshotName.startsWith("VHD-") && !snapshotName.endsWith(".vhd")) {
            snapshotName = snapshotName + ".vhd";
        }
        snapshotPath = snapshotPath.substring(0, index);

        snapshotPath = srcMountPoint + File.separator + snapshotPath;
        String destMountPoint = getRootDir(destDataStore.getUrl(), _nfsVersion);
        String destPath = destMountPoint + File.separator + destData.getPath();

        String errMsg = null;
        try {
            _storage.mkdir(destPath);

            String templateUuid = UUID.randomUUID().toString();
            String templateName = templateUuid + ".vhd";
            Script command = new Script(createTemplateFromSnapshotXenScript, cmd.getWait() * 1000L, logger);
            command.add("-p", snapshotPath);
            command.add("-s", snapshotName);
            command.add("-n", templateName);
            command.add("-t", destPath);
            String result = command.execute();

            if (result != null && !result.equalsIgnoreCase("")) {
                return new CopyCmdAnswer(result);
            }

            Map<String, Object> params = new HashMap<String, Object>();
            params.put(StorageLayer.InstanceConfigKey, _storage);
            Processor processor = new VhdProcessor();

            processor.configure("Vhd Processor", params);
            FormatInfo info = processor.process(destPath, null, templateUuid);

            TemplateLocation loc = new TemplateLocation(_storage, destPath);
            loc.create(1, true, templateUuid);
            loc.addFormat(info);
            loc.save();
            TemplateProp prop = loc.getTemplateInfo();
            TemplateObjectTO newTemplate = new TemplateObjectTO();
            newTemplate.setPath(destData.getPath() + File.separator + templateName);
            newTemplate.setFormat(ImageFormat.VHD);
            newTemplate.setSize(prop.getSize());
            newTemplate.setPhysicalSize(prop.getPhysicalSize());
            newTemplate.setName(templateUuid);
            return new CopyCmdAnswer(newTemplate);
        } catch (ConfigurationException e) {
            logger.debug("Failed to create template from snapshot: " + e.toString());
            errMsg = e.toString();
        } catch (InternalErrorException e) {
            logger.debug("Failed to create template from snapshot: " + e.toString());
            errMsg = e.toString();
        } catch (IOException e) {
            logger.debug("Failed to create template from snapshot: " + e.toString());
            errMsg = e.toString();
        }

        return new CopyCmdAnswer(errMsg);
    }

    protected Answer copySnapshotToTemplateFromNfsToNfs(CopyCommand cmd, SnapshotObjectTO srcData, NfsTO srcDataStore, TemplateObjectTO destData, NfsTO destDataStore) {

        if (srcData.getHypervisorType() == HypervisorType.XenServer) {
            return copySnapshotToTemplateFromNfsToNfsXenserver(cmd, srcData, srcDataStore, destData, destDataStore);
        } else if (srcData.getHypervisorType() == HypervisorType.KVM) {
            File srcFile = getFile(srcData.getPath(), srcDataStore.getUrl(), _nfsVersion);
            File destFile = getFile(destData.getPath(), destDataStore.getUrl(), _nfsVersion);

            VolumeObjectTO volumeObjectTO = srcData.getVolume();
            ImageFormat srcFormat = null;
            //TODO: the image format should be stored in snapshot table, instead of getting from volume
            if (volumeObjectTO != null) {
                srcFormat = volumeObjectTO.getFormat();
            } else {
                srcFormat = ImageFormat.QCOW2;
            }

            // get snapshot file name
            String templateName = srcFile.getName();
            // add kvm file extension for copied template name
            String fileName = templateName + "." + srcFormat.getFileExtension();
            String destFileFullPath = destFile.getAbsolutePath() + File.separator + fileName;
            logger.debug("copy snapshot " + srcFile.getAbsolutePath() + " to template " + destFileFullPath);
            Script.runSimpleBashScript("cp " + srcFile.getAbsolutePath() + " " + destFileFullPath);
            String metaFileName = destFile.getAbsolutePath() + File.separator + _tmpltpp;
            File metaFile = new File(metaFileName);
            try {
                _storage.create(destFile.getAbsolutePath(), _tmpltpp);
                try ( // generate template.properties file
                        FileWriter writer = new FileWriter(metaFile); BufferedWriter bufferWriter = new BufferedWriter(writer);) {
                    // KVM didn't change template unique name, just used the template name passed from orchestration layer, so no need
                    // to send template name back.
                    bufferWriter.write("uniquename=" + destData.getName());
                    bufferWriter.write("\n");
                    bufferWriter.write("filename=" + fileName);
                }
                try {
                    /**
                     * Snapshots might be in either QCOW2 or RAW image format
                     *
                     * For example RBD snapshots are in RAW format
                     */
                    Processor processor = null;
                    if (srcFormat == ImageFormat.QCOW2) {
                        processor = new QCOW2Processor();
                    } else if (srcFormat == ImageFormat.RAW) {
                        processor = new RawImageProcessor();
                    } else {
                        throw new ConfigurationException("Unknown image format " + srcFormat.toString());
                    }

                    Map<String, Object> params = new HashMap<String, Object>();
                    params.put(StorageLayer.InstanceConfigKey, _storage);

                    processor.configure("template processor", params);
                    String destPath = destFile.getAbsolutePath();

                    FormatInfo info = processor.process(destPath, null, templateName);
                    TemplateLocation loc = new TemplateLocation(_storage, destPath);
                    loc.create(1, true, destData.getName());
                    loc.addFormat(info);
                    loc.save();

                    TemplateProp prop = loc.getTemplateInfo();
                    TemplateObjectTO newTemplate = new TemplateObjectTO();
                    newTemplate.setPath(destData.getPath() + File.separator + fileName);
                    newTemplate.setFormat(srcFormat);
                    newTemplate.setSize(prop.getSize());
                    newTemplate.setPhysicalSize(prop.getPhysicalSize());
                    return new CopyCmdAnswer(newTemplate);
                } catch (ConfigurationException e) {
                    logger.debug("Failed to create template:" + e.toString());
                    return new CopyCmdAnswer(e.toString());
                } catch (InternalErrorException e) {
                    logger.debug("Failed to create template:" + e.toString());
                    return new CopyCmdAnswer(e.toString());
                }
            } catch (IOException e) {
                logger.debug("Failed to create template:" + e.toString());
                return new CopyCmdAnswer(e.toString());
            }
        }

        return new CopyCmdAnswer("");
    }

    protected File getFile(String path, String nfsPath, String nfsVersion) {
        String filePath = getRootDir(nfsPath, nfsVersion) + File.separator + path;
        File f = new File(filePath);
        if (!f.exists()) {
            f = findFile(filePath);
            if (f == null) {
                _storage.mkdirs(filePath);
                f = new File(filePath);
            }
        }
        return f;
    }

    protected Answer createTemplateFromSnapshot(CopyCommand cmd) {
        DataTO srcData = cmd.getSrcTO();
        DataTO destData = cmd.getDestTO();
        DataStoreTO srcDataStore = srcData.getDataStore();
        DataStoreTO destDataStore = destData.getDataStore();
        if (srcDataStore.getRole() == DataStoreRole.Image || srcDataStore.getRole() == DataStoreRole.ImageCache || srcDataStore.getRole() == DataStoreRole.Primary) {
            if (!(srcDataStore instanceof NfsTO)) {
                logger.debug("only support nfs storage as src, when create template from snapshot");
                return Answer.createUnsupportedCommandAnswer(cmd);
            }

            if (destDataStore instanceof NfsTO) {
                return copySnapshotToTemplateFromNfsToNfs(cmd, (SnapshotObjectTO)srcData, (NfsTO)srcDataStore, (TemplateObjectTO)destData, (NfsTO)destDataStore);
            } else if (destDataStore instanceof SwiftTO) {
                //create template on the same data store
                CopyCmdAnswer answer = (CopyCmdAnswer)copySnapshotToTemplateFromNfsToNfs(cmd, (SnapshotObjectTO)srcData, (NfsTO)srcDataStore, (TemplateObjectTO)destData,
                        (NfsTO)srcDataStore);
                if (!answer.getResult()) {
                    return answer;
                }
                logger.debug("starting copy template to swift");
                TemplateObjectTO newTemplate = (TemplateObjectTO)answer.getNewData();
                newTemplate.setDataStore(srcDataStore);
                CopyCommand newCpyCmd = new CopyCommand(newTemplate, destData, cmd.getWait(), cmd.executeInSequence());
                Answer result = copyFromNfsToSwift(newCpyCmd);

                cleanupStagingNfs(newTemplate);
                return result;

            } else if (destDataStore instanceof S3TO) {
                //create template on the same data store
                CopyCmdAnswer answer = (CopyCmdAnswer)copySnapshotToTemplateFromNfsToNfs(cmd, (SnapshotObjectTO)srcData, (NfsTO)srcDataStore, (TemplateObjectTO)destData,
                        (NfsTO)srcDataStore);
                if (!answer.getResult()) {
                    return answer;
                }
                TemplateObjectTO newTemplate = (TemplateObjectTO)answer.getNewData();
                newTemplate.setDataStore(srcDataStore);
                CopyCommand newCpyCmd = new CopyCommand(newTemplate, destData, cmd.getWait(), cmd.executeInSequence());
                Answer result = copyFromNfsToS3(newCpyCmd);

                cleanupStagingNfs(newTemplate);

                return result;
            }
        }
        logger.debug("Failed to create template from snapshot");
        return new CopyCmdAnswer("Unsupported protocol");
    }

    /**
     * clean up template data on staging area
     * @param newTemplate: The template on the secondary storage that needs to be cleaned up
     */
    protected void cleanupStagingNfs(TemplateObjectTO newTemplate) {
        try {
            DeleteCommand deleteCommand = new DeleteCommand(newTemplate);
            execute(deleteCommand);
        } catch (Exception e) {
            logger.debug("Failed to clean up staging area:", e);
        }
    }

    protected Answer copyFromNfsToImage(CopyCommand cmd) {
        DataTO destData = cmd.getDestTO();
        DataStoreTO destDataStore = destData.getDataStore();

        if (destDataStore instanceof S3TO) {
            return copyFromNfsToS3(cmd);
        } else if (destDataStore instanceof SwiftTO) {
            return copyFromNfsToSwift(cmd);
        } else {
            return new CopyCmdAnswer("unsupported ");
        }
    }

    private boolean shouldPerformDataMigration(DataTO srcData, DataTO destData) {
        DataStoreTO srcDataStore = srcData.getDataStore();
        DataStoreTO destDataStore = destData.getDataStore();
        if (DataStoreRole.Image == srcDataStore.getRole() && DataStoreRole.Image == destDataStore.getRole() &&
                srcDataStore instanceof NfsTO && destDataStore instanceof NfsTO &&
                ((srcData.getObjectType() == DataObjectType.TEMPLATE && destData.getObjectType() == DataObjectType.TEMPLATE) ||
                        (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.SNAPSHOT) ||
                        (srcData.getObjectType() == DataObjectType.VOLUME && destData.getObjectType() == DataObjectType.VOLUME))) {
            return true;
        }
        return false;
    }

    protected Answer execute(CopyCommand cmd) {
        DataTO srcData = cmd.getSrcTO();
        DataTO destData = cmd.getDestTO();
        DataStoreTO srcDataStore = srcData.getDataStore();
        DataStoreTO destDataStore = destData.getDataStore();
        if (shouldPerformDataMigration(srcData, destData)) {
            return copyFromNfsToNfs(cmd);
        }

        if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.TEMPLATE) {
            return createTemplateFromSnapshot(cmd);
        }

        if (destDataStore instanceof NfsTO && destDataStore.getRole() == DataStoreRole.ImageCache) {
            NfsTO destImageStore = (NfsTO)destDataStore;
            if (srcDataStore instanceof S3TO) {
                S3TO s3 = (S3TO)srcDataStore;
                return copyFromS3ToNfs(cmd, srcData, s3, destData, destImageStore);
            } else if (srcDataStore instanceof SwiftTO) {
                return copyFromSwiftToNfs(cmd, srcData, (SwiftTO)srcDataStore, destData, destImageStore);
            }
        }

        if (srcDataStore.getRole() == DataStoreRole.ImageCache && destDataStore.getRole() == DataStoreRole.Image) {
            return copyFromNfsToImage(cmd);
        }

        return Answer.createUnsupportedCommandAnswer(cmd);
    }

    protected String determineS3TemplateDirectory(final Long accountId, final Long templateId, final String templateUniqueName) {
        return StringUtils.join(asList(TEMPLATE_ROOT_DIR, accountId, templateId, templateUniqueName), S3Utils.SEPARATOR);
    }

    private String determineS3TemplateNameFromKey(String key) {
        return StringUtils.substringAfterLast(StringUtils.substringBeforeLast(key, S3Utils.SEPARATOR), S3Utils.SEPARATOR);
    }

    protected String determineS3VolumeDirectory(final Long accountId, final Long volId) {
        return StringUtils.join(asList(VOLUME_ROOT_DIR, accountId, volId), S3Utils.SEPARATOR);
    }

    protected Long determineS3VolumeIdFromKey(String key) {
        return Long.parseLong(StringUtils.substringAfterLast(StringUtils.substringBeforeLast(key, S3Utils.SEPARATOR), S3Utils.SEPARATOR));
    }

    private String determineStorageTemplatePath(final String storagePath, String dataPath, String nfsVersion) {
        return StringUtils.join(asList(getRootDir(storagePath, nfsVersion), dataPath), File.separator);
    }

    protected File downloadFromUrlToNfs(String url, NfsTO nfs, String path, String name) {
        HttpClient client = new DefaultHttpClient();
        HttpGet get = new HttpGet(url);
        try {
            HttpResponse response = client.execute(get);
            HttpEntity entity = response.getEntity();
            if (entity == null) {
                logger.debug("Faled to get entity");
                throw new CloudRuntimeException("Failed to get url: " + url);
            }

            String nfsMountPath = getRootDir(nfs.getUrl(), _nfsVersion);

            String filePath = nfsMountPath + File.separator + path;
            File directory = new File(filePath);
            if (!directory.exists()) {
                _storage.mkdirs(filePath);
            }
            File destFile = new File(filePath + File.separator + name);
            if (!destFile.createNewFile()) {
                logger.warn("Reusing existing file " + destFile.getPath());
            }
            try (FileOutputStream outputStream = new FileOutputStream(destFile);) {
                entity.writeTo(outputStream);
            } catch (IOException e) {
                logger.debug("downloadFromUrlToNfs:Exception:" + e.getMessage(), e);
            }
            return new File(destFile.getAbsolutePath());
        } catch (IOException e) {
            logger.debug("Failed to get url: " + url + ", due to " + e.toString());
            throw new CloudRuntimeException(e);
        }
    }

    protected Answer registerTemplateOnSwift(DownloadCommand cmd) {
        SwiftTO swiftTO = (SwiftTO)cmd.getDataStore();
        String path = cmd.getInstallPath();
        DataStoreTO cacheStore = cmd.getCacheStore();
        if (cacheStore == null || !(cacheStore instanceof NfsTO)) {
            return new DownloadAnswer("cache store can't be null", VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
        }

        File file = null;
        try {
            NfsTO nfsCacheStore = (NfsTO)cacheStore;
            String fileName = cmd.getName() + "." + cmd.getFormat().getFileExtension();
            file = downloadFromUrlToNfs(cmd.getUrl(), nfsCacheStore, path, fileName);
            String container = "T-" + cmd.getId();
            String swiftPath = SwiftUtil.putObject(swiftTO, file, container, null);

            long virtualSize = getVirtualSize(file, getTemplateFormat(file.getName()));
            long size = file.length();
            String uniqueName = cmd.getName();

            //put metda file
            File uniqDir = _storage.createUniqDir();
            String metaFileName = uniqDir.getAbsolutePath() + File.separator + _tmpltpp;
            _storage.create(uniqDir.getAbsolutePath(), _tmpltpp);

            File metaFile = swiftWriteMetadataFile(metaFileName, uniqueName, fileName, size, virtualSize);

            SwiftUtil.putObject(swiftTO, metaFile, container, _tmpltpp);
            metaFile.delete();
            uniqDir.delete();
            String md5sum = null;
            try (FileInputStream fs = new FileInputStream(file)) {
                md5sum = DigestUtils.md5Hex(fs);
            } catch (IOException e) {
                logger.debug("Failed to get md5sum: " + file.getAbsoluteFile());
            }

            DownloadAnswer answer = new DownloadAnswer(null, 100, null, VMTemplateStorageResourceAssoc.Status.DOWNLOADED, swiftPath, swiftPath, virtualSize, file.length(), md5sum);
            return answer;
        } catch (IOException e) {
            logger.debug("Failed to register template into swift", e);
            return new DownloadAnswer(e.toString(), VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
        } finally {
            if (file != null) {
                file.delete();
            }
        }
    }

    private Answer execute(DownloadCommand cmd) {
        DataStoreTO dstore = cmd.getDataStore();
        if (dstore instanceof NfsTO || dstore instanceof S3TO) {
            return _dlMgr.handleDownloadCommand(this, cmd);
        } else if (dstore instanceof SwiftTO) {
            return registerTemplateOnSwift(cmd);
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + dstore);
        }

    }

    private ImageFormat getTemplateFormat(String filePath) {
        String ext = null;
        int extensionPos = filePath.lastIndexOf('.');
        int lastSeparator = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
        int i = lastSeparator > extensionPos ? -1 : extensionPos;
        if (i > 0) {
            ext = filePath.substring(i + 1);
        }
        if (ext != null) {
            if (ext.equalsIgnoreCase("vhd")) {
                return ImageFormat.VHD;
            } else if (ext.equalsIgnoreCase("vhdx")) {
                return ImageFormat.VHDX;
            } else if (ext.equalsIgnoreCase("qcow2")) {
                return ImageFormat.QCOW2;
            } else if (ext.equalsIgnoreCase("ova")) {
                return ImageFormat.OVA;
            } else if (ext.equalsIgnoreCase("tar")) {
                return ImageFormat.TAR;
            } else if (ext.equalsIgnoreCase("img") || ext.equalsIgnoreCase("raw")) {
                return ImageFormat.RAW;
            } else if (ext.equalsIgnoreCase("vmdk")) {
                return ImageFormat.VMDK;
            } else if (ext.equalsIgnoreCase("vdi")) {
                return ImageFormat.VDI;
            }
        }

        return null;

    }

    protected long getVirtualSize(File file, ImageFormat format) {
        Processor processor = null;
        try {
            if (format == null) {
                return file.length();
            } else if (format == ImageFormat.QCOW2) {
                processor = new QCOW2Processor();
            } else if (format == ImageFormat.OVA) {
                processor = new OVAProcessor();
            } else if (format == ImageFormat.VHD) {
                processor = new VhdProcessor();
            } else if (format == ImageFormat.RAW) {
                processor = new RawImageProcessor();
            } else if (format == ImageFormat.VMDK) {
                processor = new VmdkProcessor();
            }
            if (format == ImageFormat.TAR) {
                processor = new TARProcessor();
            }

            if (processor == null) {
                return file.length();
            }

            Map<String, Object> params = new HashMap<String, Object>();
            params.put(StorageLayer.InstanceConfigKey, _storage);
            processor.configure("template processor", params);
            return processor.getVirtualSize(file);
        } catch (Exception e) {
            logger.warn("Failed to get virtual size of file " + file.getPath() + ", returning file size instead: ", e);
            return file.length();
        }

    }

    protected File findFile(String path) {
        File srcFile = _storage.getFile(path);
        if (!srcFile.exists()) {
            srcFile = _storage.getFile(path + ".qcow2");
            if (!srcFile.exists()) {
                srcFile = _storage.getFile(path + ".vhd");
                if (!srcFile.exists()) {
                    srcFile = _storage.getFile(path + ".ova");
                    if (!srcFile.exists()) {
                        srcFile = _storage.getFile(path + ".vmdk");
                        if (!srcFile.exists()) {
                            return null;
                        }
                    }
                }
            }
        }

        return srcFile;
    }

    protected Answer copyFromNfsToNfs(CopyCommand cmd) {
        final DataTO srcData = cmd.getSrcTO();
        final DataTO destData = cmd.getDestTO();
        DataStoreTO srcDataStore = srcData.getDataStore();
        NfsTO srcStore = (NfsTO)srcDataStore;
        DataStoreTO destDataStore = destData.getDataStore();
        final NfsTO destStore = (NfsTO) destDataStore;
        try {
            File srcFile = new File(getDir(srcStore.getUrl(), _nfsVersion), srcData.getPath());
            File destFile = new File(getDir(destStore.getUrl(), _nfsVersion), destData.getPath());

            if (srcFile == null) {
                return new CopyCmdAnswer("Can't find source file at path: "+ srcData.getPath() +" on datastore: "+ srcDataStore.getUuid() +" to initiate file transfer");
            }
            ImageFormat format = getTemplateFormat(srcFile.getName());
            if (srcData instanceof TemplateObjectTO || srcData instanceof VolumeObjectTO) {
                File srcDir = null;
                if (srcFile.isFile() || srcFile.getName().contains(".")) {
                    srcDir = new File(srcFile.getParent());
                } else if (!srcFile.isDirectory()) {
                    srcDir = new File(srcFile.getParent());
                } else if (srcFile.isDirectory() && Arrays.stream(srcData.getPath().split(File.separator)).count() == 4) {
                    destFile = new File(destFile.getPath(), srcFile.getName());
                }
                File destDir = null;
                if (destFile.isFile()) {
                    destDir = new File(destFile.getParent());
                }
                try {
                    FileUtils.copyDirectory((srcDir == null ? srcFile : srcDir), (destDir == null? destFile : destDir));
                } catch (IOException e) {
                    String msg = "Failed to copy file to destination";
                    logger.info(msg);
                    return new CopyCmdAnswer(msg);
                }
            } else {
                destFile = new File(destFile, srcFile.getName());
                try {
                if (srcFile.isFile()) {
                    FileUtils.copyFile(srcFile, destFile);
                } else {
                    // for vmware
                    srcFile = new File(srcFile.getParent());
                    FileUtils.copyDirectory(srcFile, destFile);
                }
                } catch (IOException e) {
                    String msg = "Failed to copy file to destination";
                    logger.info(msg);
                    return new CopyCmdAnswer(msg);
                }
            }

            DataTO retObj = null;
            if (destData.getObjectType() == DataObjectType.TEMPLATE) {
                TemplateObjectTO newTemplate = new TemplateObjectTO();
                newTemplate.setPath(destData.getPath() + File.separator + srcFile.getName());
                newTemplate.setSize(getVirtualSize(srcFile, format));
                newTemplate.setPhysicalSize(srcFile.length());
                newTemplate.setFormat(format);
                retObj = newTemplate;
            } else if (destData.getObjectType() == DataObjectType.VOLUME) {
                VolumeObjectTO newVol = new VolumeObjectTO();
                if (srcFile.isFile()) {
                    newVol.setPath(destData.getPath() + File.separator + srcFile.getName());
                } else {
                    newVol.setPath(srcData.getPath());
                }
                newVol.setSize(getVirtualSize(srcFile, format));
                retObj = newVol;
            } else if (destData.getObjectType() == DataObjectType.SNAPSHOT) {
                SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
                if (srcFile.isFile()) {
                    newSnapshot.setPath(destData.getPath() + File.separator + destFile.getName());
                } else {
                    newSnapshot.setPath(destData.getPath() + File.separator + destFile.getName() + File.separator + destFile.getName());
                }
                retObj = newSnapshot;
            }
            return new CopyCmdAnswer(retObj);
            } catch (Exception e) {
                logger.error("failed to copy file" + srcData.getPath(), e);
                return new CopyCmdAnswer("failed to copy file" + srcData.getPath() + e.toString());
        }
    }

    protected Answer copyFromNfsToS3(CopyCommand cmd) {
        final DataTO srcData = cmd.getSrcTO();
        final DataTO destData = cmd.getDestTO();
        DataStoreTO srcDataStore = srcData.getDataStore();
        NfsTO srcStore = (NfsTO)srcDataStore;
        DataStoreTO destDataStore = destData.getDataStore();

        final S3TO s3 = (S3TO)destDataStore;

        try {
            final String templatePath = determineStorageTemplatePath(srcStore.getUrl(), srcData.getPath(), _nfsVersion);

            if (logger.isDebugEnabled()) {
                logger.debug("Found " + srcData.getObjectType() + " from directory " + templatePath + " to upload to S3.");
            }

            final String bucket = s3.getBucketName();
            File srcFile = findFile(templatePath);
            if (srcFile == null) {
                return new CopyCmdAnswer("Can't find src file:" + templatePath);
            }

            ImageFormat format = getTemplateFormat(srcFile.getName());
            String key = destData.getPath() + S3Utils.SEPARATOR + srcFile.getName();

            putFile(s3, srcFile, bucket, key).waitForCompletion();

            DataTO retObj = null;
            if (destData.getObjectType() == DataObjectType.TEMPLATE) {
                TemplateObjectTO newTemplate = new TemplateObjectTO();
                newTemplate.setPath(key);
                newTemplate.setSize(getVirtualSize(srcFile, format));
                newTemplate.setPhysicalSize(srcFile.length());
                newTemplate.setFormat(format);
                retObj = newTemplate;
            } else if (destData.getObjectType() == DataObjectType.VOLUME) {
                VolumeObjectTO newVol = new VolumeObjectTO();
                newVol.setPath(key);
                newVol.setSize(srcFile.length());
                retObj = newVol;
            } else if (destData.getObjectType() == DataObjectType.SNAPSHOT) {
                SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
                newSnapshot.setPath(key);
                retObj = newSnapshot;
            }

            return new CopyCmdAnswer(retObj);
        } catch (Exception e) {
            logger.error("failed to upload" + srcData.getPath(), e);
            return new CopyCmdAnswer("failed to upload" + srcData.getPath() + e.toString());
        }
    }

    /***
     *This method will create a file using the filenName and metaFileName.
     *That file will contain the given attributes (unique name, file name, size, and virtualSize).
     *
     * @param metaFileName : The path of the metadata file
     * @param filename      :attribute:  Filename of the template
     * @param uniqueName    :attribute:  Unique name of the template
     * @param size          :attribute:  physical size of the template
     * @param virtualSize   :attribute:  virtual size of the template
     * @return File representing the metadata file
     * @throws IOException
     */

    protected File swiftWriteMetadataFile(String metaFileName, String uniqueName, String filename, long size, long virtualSize) throws IOException {
        File metaFile = new File(metaFileName);
        FileWriter writer = new FileWriter(metaFile);
        BufferedWriter bufferWriter = new BufferedWriter(writer);
        bufferWriter.write("uniquename=" + uniqueName);
        bufferWriter.write("\n");
        bufferWriter.write("filename=" + filename);
        bufferWriter.write("\n");
        bufferWriter.write("size=" + size);
        bufferWriter.write("\n");
        bufferWriter.write("virtualsize=" + virtualSize);
        bufferWriter.close();
        writer.close();
        return metaFile;
    }

    /**
     * Creates a template.properties for Swift with its correct unique name
     *
     * @param swift  The swift object
     * @param srcFile Source file on the staging NFS
     * @param containerName Destination container  @return true on successful write
     * @param uniqueName Unique name identifying the template
     */
    protected boolean swiftUploadMetadataFile(SwiftTO swift, File srcFile, String containerName, String uniqueName) throws IOException {

        File uniqDir = _storage.createUniqDir();
        String metaFileName = uniqDir.getAbsolutePath() + File.separator + _tmpltpp;
        _storage.create(uniqDir.getAbsolutePath(), _tmpltpp);

        long virtualSize = getVirtualSize(srcFile, getTemplateFormat(srcFile.getName()));

        File metaFile = swiftWriteMetadataFile(metaFileName, uniqueName, srcFile.getName(), srcFile.length(), virtualSize);

        SwiftUtil.putObject(swift, metaFile, containerName, _tmpltpp);
        metaFile.delete();
        uniqDir.delete();

        return true;
    }

    /**
     * Copies data from NFS and uploads it into a Swift container
     *
     * @param cmd CopyComand
     * @return CopyCmdAnswer
     */
    protected Answer copyFromNfsToSwift(CopyCommand cmd) {

        final DataTO srcData = cmd.getSrcTO();
        final DataTO destData = cmd.getDestTO();

        DataStoreTO srcDataStore = srcData.getDataStore();
        NfsTO srcStore = (NfsTO)srcDataStore;
        DataStoreTO destDataStore = destData.getDataStore();
        File srcFile = getFile(srcData.getPath(), srcStore.getUrl(), _nfsVersion);

        SwiftTO swift = (SwiftTO)destDataStore;
        long pathId = destData.getId();

        try {

            if (destData instanceof SnapshotObjectTO) {
                pathId = ((SnapshotObjectTO)destData).getVolume().getId();
            }

            String containerName = SwiftUtil.getContainerName(destData.getObjectType().toString(), pathId);
            String swiftPath = SwiftUtil.putObject(swift, srcFile, containerName, srcFile.getName());

            DataTO retObj = null;
            if (destData.getObjectType() == DataObjectType.TEMPLATE) {
                TemplateObjectTO destTemplateData = (TemplateObjectTO)destData;
                String uniqueName = destTemplateData.getName();
                swiftUploadMetadataFile(swift, srcFile, containerName, uniqueName);
                TemplateObjectTO newTemplate = new TemplateObjectTO();
                newTemplate.setPath(swiftPath);
                newTemplate.setSize(getVirtualSize(srcFile, getTemplateFormat(srcFile.getName())));
                newTemplate.setPhysicalSize(srcFile.length());
                newTemplate.setFormat(getTemplateFormat(srcFile.getName()));
                retObj = newTemplate;
            } else if (destData.getObjectType() == DataObjectType.VOLUME) {
                VolumeObjectTO newVol = new VolumeObjectTO();
                newVol.setPath(containerName);
                newVol.setSize(getVirtualSize(srcFile, getTemplateFormat(srcFile.getName())));
                retObj = newVol;
            } else if (destData.getObjectType() == DataObjectType.SNAPSHOT) {
                SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
                newSnapshot.setPath(containerName + File.separator + srcFile.getName());
                retObj = newSnapshot;
            }

            return new CopyCmdAnswer(retObj);

        } catch (Exception e) {
            logger.error("failed to upload " + srcData.getPath(), e);
            return new CopyCmdAnswer("failed to upload " + srcData.getPath() + e.toString());
        }
    }

    String swiftDownload(SwiftTO swift, String container, String rfilename, String lFullPath) {
        Script command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("/usr/bin/python /usr/local/cloud/systemvm/scripts/storage/secondary/swift -A " + swift.getUrl() + " -U " + swift.getAccount() + ":" + swift.getUserName()
        + " -K " + swift.getKey() + " download " + container + " " + rfilename + " -o " + lFullPath);
        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
        String result = command.execute(parser);
        if (result != null) {
            String errMsg = "swiftDownload failed  err=" + result;
            logger.warn(errMsg);
            return errMsg;
        }
        if (parser.getLines() != null) {
            String[] lines = parser.getLines().split("\\n");
            for (String line : lines) {
                if (line.contains("Errno") || line.contains("failed")) {
                    String errMsg = "swiftDownload failed , err=" + parser.getLines();
                    logger.warn(errMsg);
                    return errMsg;
                }
            }
        }
        return null;

    }

    String swiftDownloadContainer(SwiftTO swift, String container, String ldir) {
        Script command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("cd " + ldir + ";/usr/bin/python /usr/local/cloud/systemvm/scripts/storage/secondary/swift -A " + swift.getUrl() + " -U " + swift.getAccount() + ":"
                + swift.getUserName() + " -K " + swift.getKey() + " download " + container);
        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
        String result = command.execute(parser);
        if (result != null) {
            String errMsg = "swiftDownloadContainer failed  err=" + result;
            logger.warn(errMsg);
            return errMsg;
        }
        if (parser.getLines() != null) {
            String[] lines = parser.getLines().split("\\n");
            for (String line : lines) {
                if (line.contains("Errno") || line.contains("failed")) {
                    String errMsg = "swiftDownloadContainer failed , err=" + parser.getLines();
                    logger.warn(errMsg);
                    return errMsg;
                }
            }
        }
        return null;

    }

    String swiftUpload(SwiftTO swift, String container, String lDir, String lFilename) {
        long SWIFT_MAX_SIZE = 5L * 1024L * 1024L * 1024L;
        List<String> files = new ArrayList<String>();
        if (lFilename.equals("*")) {
            File dir = new File(lDir);
            String[] dir_lst = dir.list();
            if (dir_lst != null) {
                for (String file : dir_lst) {
                    if (file.startsWith(".")) {
                        continue;
                    }
                    files.add(file);
                }
            }
        } else {
            files.add(lFilename);
        }

        for (String file : files) {
            File f = new File(lDir + "/" + file);
            long size = f.length();
            Script command = new Script("/bin/bash", logger);
            command.add("-c");
            if (size <= SWIFT_MAX_SIZE) {
                command.add("cd " + lDir + ";/usr/bin/python /usr/local/cloud/systemvm/scripts/storage/secondary/swift -A " + swift.getUrl() + " -U " + swift.getAccount() + ":"
                        + swift.getUserName() + " -K " + swift.getKey() + " upload " + container + " " + file);
            } else {
                command.add("cd " + lDir + ";/usr/bin/python /usr/local/cloud/systemvm/scripts/storage/secondary/swift -A " + swift.getUrl() + " -U " + swift.getAccount() + ":"
                        + swift.getUserName() + " -K " + swift.getKey() + " upload -S " + SWIFT_MAX_SIZE + " " + container + " " + file);
            }
            OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
            String result = command.execute(parser);
            if (result != null) {
                String errMsg = "swiftUpload failed , err=" + result;
                logger.warn(errMsg);
                return errMsg;
            }
            if (parser.getLines() != null) {
                String[] lines = parser.getLines().split("\\n");
                for (String line : lines) {
                    if (line.contains("Errno") || line.contains("failed")) {
                        String errMsg = "swiftUpload failed , err=" + parser.getLines();
                        logger.warn(errMsg);
                        return errMsg;
                    }
                }
            }
        }

        return null;
    }

    String[] swiftList(SwiftTO swift, String container, String rFilename) {
        Script command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("/usr/bin/python /usr/local/cloud/systemvm/scripts/storage/secondary/swift -A " + swift.getUrl() + " -U " + swift.getAccount() + ":" + swift.getUserName()
        + " -K " + swift.getKey() + " list " + container + " " + rFilename);
        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
        String result = command.execute(parser);
        if (result == null && parser.getLines() != null) {
            String[] lines = parser.getLines().split("\\n");
            return lines;
        } else {
            if (result != null) {
                String errMsg = "swiftList failed , err=" + result;
                logger.warn(errMsg);
            } else {
                String errMsg = "swiftList failed, no lines returns";
                logger.warn(errMsg);
            }
        }
        return null;
    }

    String swiftDelete(SwiftTO swift, String container, String object) {
        Script command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("/usr/bin/python /usr/local/cloud/systemvm/scripts/storage/secondary/swift -A " + swift.getUrl() + " -U " + swift.getAccount() + ":" + swift.getUserName()
        + " -K " + swift.getKey() + " delete " + container + " " + object);
        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
        String result = command.execute(parser);
        if (result != null) {
            String errMsg = "swiftDelete failed , err=" + result;
            logger.warn(errMsg);
            return errMsg;
        }
        if (parser.getLines() != null) {
            String[] lines = parser.getLines().split("\\n");
            for (String line : lines) {
                if (line.contains("Errno") || line.contains("failed")) {
                    String errMsg = "swiftDelete failed , err=" + parser.getLines();
                    logger.warn(errMsg);
                    return errMsg;
                }
            }
        }
        return null;
    }

    public Answer execute(DeleteSnapshotsDirCommand cmd) {
        DataStoreTO dstore = cmd.getDataStore();
        if (dstore instanceof NfsTO) {
            NfsTO nfs = (NfsTO)dstore;
            String relativeSnapshotPath = cmd.getDirectory();
            String parent = getRootDir(nfs.getUrl(), _nfsVersion);

            if (relativeSnapshotPath.startsWith(File.separator)) {
                relativeSnapshotPath = relativeSnapshotPath.substring(1);
            }

            if (!parent.endsWith(File.separator)) {
                parent += File.separator;
            }
            String absoluteSnapshotPath = parent + relativeSnapshotPath;
            File snapshotDir = new File(absoluteSnapshotPath);
            String details = null;
            if (!snapshotDir.exists()) {
                details = "snapshot directory " + snapshotDir.getName() + " doesn't exist";
                logger.debug(details);
                return new Answer(cmd, true, details);
            }
            // delete all files in the directory
            String lPath = absoluteSnapshotPath + "/*";
            String result = deleteLocalFile(lPath);
            if (result != null) {
                String errMsg = "failed to delete all snapshots " + lPath + " , err=" + result;
                logger.warn(errMsg);
                return new Answer(cmd, false, errMsg);
            }
            // delete the directory
            if (!snapshotDir.delete()) {
                details = "Unable to delete directory " + snapshotDir.getName() + " under snapshot path " + relativeSnapshotPath;
                logger.debug(details);
                return new Answer(cmd, false, details);
            }
            return new Answer(cmd, true, null);
        } else if (dstore instanceof S3TO) {
            final S3TO s3 = (S3TO)dstore;
            final String path = cmd.getDirectory();
            final String bucket = s3.getBucketName();
            try {
                S3Utils.deleteDirectory(s3, bucket, path);
                return new Answer(cmd, true, String.format("Deleted snapshot %1%s from bucket %2$s.", path, bucket));
            } catch (Exception e) {
                final String errorMessage = String.format("Failed to delete snapshot %1$s from bucket %2$s due to the following error: %3$s", path, bucket, e.getMessage());
                logger.error(errorMessage, e);
                return new Answer(cmd, false, errorMessage);
            }
        } else if (dstore instanceof SwiftTO) {
            String path = cmd.getDirectory();
            String volumeId = StringUtils.substringAfterLast(path, "/"); // assuming
            // that
            // the
            // filename
            // is
            // the
            // last
            // section
            // in
            // the
            // path
            String result = swiftDelete((SwiftTO)dstore, "V-" + volumeId.toString(), "");
            if (result != null) {
                String errMsg = "failed to delete snapshot for volume " + volumeId + " , err=" + result;
                logger.warn(errMsg);
                return new Answer(cmd, false, errMsg);
            }
            return new Answer(cmd, true, "Deleted snapshot " + path + " from swift");
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + dstore);
        }
    }

    private Answer execute(ComputeChecksumCommand cmd) {

        String relativeTemplatePath = cmd.getTemplatePath();
        DataStoreTO store = cmd.getStore();
        if (!(store instanceof NfsTO)) {
            return new Answer(cmd, false, "can't handle non nfs data store");
        }
        NfsTO nfsStore = (NfsTO)store;
        String parent = getRootDir(nfsStore.getUrl(), _nfsVersion);

        if (relativeTemplatePath.startsWith(File.separator)) {
            relativeTemplatePath = relativeTemplatePath.substring(1);
        }

        if (!parent.endsWith(File.separator)) {
            parent += File.separator;
        }
        String absoluteTemplatePath = parent + relativeTemplatePath;
        String algorithm = cmd.getAlgorithm();
        File f = new File(absoluteTemplatePath);
        if (logger.isDebugEnabled()) {
            logger.debug("parent path " + parent + " relative template path " + relativeTemplatePath);
        }
        String checksum = null;

        try (InputStream is = new FileInputStream(f);){
            checksum = DigestHelper.digest(algorithm, is).toString();
            if (logger.isDebugEnabled()) {
                logger.debug("Successfully calculated checksum for file " + absoluteTemplatePath + " - " + checksum);
            }
        } catch (IOException e) {
            String logMsg = "Unable to process file for " + algorithm + " - " + absoluteTemplatePath;
            logger.error(logMsg);
            return new Answer(cmd, false, checksum);
        } catch (NoSuchAlgorithmException e) {
            return new Answer(cmd, false, checksum);
        }

        return new Answer(cmd, true, checksum);
    }

    private void configCerts(KeystoreManager.Certificates certs) {
        if (certs == null) {
            configureSSL();
        } else {
            String prvKey = certs.getPrivKey();
            String pubCert = certs.getPrivCert();
            String certChain = certs.getCertChain();
            String rootCACert = certs.getRootCACert();

            try {
                File prvKeyFile = File.createTempFile("prvkey", null);
                String prvkeyPath = prvKeyFile.getAbsolutePath();
                try (BufferedWriter prvt_key_file = new BufferedWriter(new FileWriter(prvKeyFile));) {
                    prvt_key_file.write(prvKey);
                } catch (IOException e) {
                    logger.debug("Failed to config ssl: " + e.toString());
                }

                File pubCertFile = File.createTempFile("pubcert", null);
                String pubCertFilePath = pubCertFile.getAbsolutePath();

                try (BufferedWriter pub_cert_file = new BufferedWriter(new FileWriter(pubCertFile));) {
                    pub_cert_file.write(pubCert);
                } catch (IOException e) {
                    logger.debug("Failed to config ssl: " + e.toString());
                }

                String certChainFilePath = null, rootCACertFilePath = null;
                File certChainFile = null, rootCACertFile = null;
                if (certChain != null) {
                    certChainFile = File.createTempFile("certchain", null);
                    certChainFilePath = certChainFile.getAbsolutePath();
                    try (BufferedWriter cert_chain_out = new BufferedWriter(new FileWriter(certChainFile));) {
                        cert_chain_out.write(certChain);
                    } catch (IOException e) {
                        logger.debug("Failed to config ssl: " + e.toString());
                    }
                }

                if (rootCACert != null) {
                    rootCACertFile = File.createTempFile("rootcert", null);
                    rootCACertFilePath = rootCACertFile.getAbsolutePath();
                    try (BufferedWriter root_ca_cert_file = new BufferedWriter(new FileWriter(rootCACertFile));) {
                        root_ca_cert_file.write(rootCACert);
                    } catch (IOException e) {
                        logger.debug("Failed to config ssl: " + e.toString());
                    }
                }

                configureSSL(prvkeyPath, pubCertFilePath, certChainFilePath, rootCACertFilePath);

                prvKeyFile.delete();
                pubCertFile.delete();
                if (certChainFile != null) {
                    certChainFile.delete();
                }
                if (rootCACertFile != null) {
                    rootCACertFile.delete();
                }

            } catch (IOException e) {
                logger.debug("Failed to config ssl: " + e.toString());
            }
        }
    }

    private Answer execute(SecStorageSetupCommand cmd) {
        if (!_inSystemVM) {
            return new Answer(cmd, true, null);
        }
        Answer answer = null;
        DataStoreTO dStore = cmd.getDataStore();
        if (dStore instanceof NfsTO) {
            String secUrl = cmd.getSecUrl();
            try {
                URI uri = new URI(secUrl);
                String nfsHostIp = getUriHostIp(uri);

                addRouteToInternalIpOrCidr(_storageGateway, _storageIp, _storageNetmask, nfsHostIp);

                String dir = mountUri(uri, cmd.getNfsVersion());

                configCerts(cmd.getCerts());

                nfsIps.add(nfsHostIp);
                answer = new SecStorageSetupAnswer(dir);
            } catch (Exception e) {
                String msg = "GetRootDir for " + secUrl + " failed due to " + e.toString();
                logger.error(msg);
                answer = new Answer(cmd, false, msg);

            }
        } else {
            // TODO: what do we need to setup for S3/Swift, maybe need to mount
            // to some cache storage
            answer = new Answer(cmd, true, null);
        }

        savePostUploadPSK(cmd.getPostUploadKey());
        startPostUploadServer();
        return answer;
    }

    private void startPostUploadServer() {
        final int PORT = 8210;
        final int NO_OF_WORKERS = 15;
        final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        final EventLoopGroup workerGroup = new NioEventLoopGroup(NO_OF_WORKERS);
        final ServerBootstrap b = new ServerBootstrap();
        final NfsSecondaryStorageResource storageResource = this;
        b.group(bossGroup, workerGroup);
        b.channel(NioServerSocketChannel.class);
        b.handler(new LoggingHandler(LogLevel.INFO));
        b.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new HttpRequestDecoder());
                pipeline.addLast(new HttpResponseEncoder());
                pipeline.addLast(new HttpContentCompressor());
                pipeline.addLast(new HttpUploadServerHandler(storageResource));
            }
        });
        new Thread() {
            @Override
            public void run() {
                try {
                    Channel ch = b.bind(PORT).sync().channel();
                    logger.info(String.format("Started post upload server on port %d with %d workers", PORT, NO_OF_WORKERS));
                    ch.closeFuture().sync();
                } catch (InterruptedException e) {
                    logger.info("Failed to start post upload server");
                    logger.debug("Exception while starting post upload server", e);
                } finally {
                    bossGroup.shutdownGracefully();
                    workerGroup.shutdownGracefully();
                    logger.info("shutting down post upload server");
                }
            }
        }.start();
        logger.info("created a thread to start post upload server");
    }

    private void savePostUploadPSK(String psk) {
        try {
            FileUtils.writeStringToFile(new File(POST_UPLOAD_KEY_LOCATION), psk, "utf-8");
        } catch (IOException ex) {
            logger.debug("Failed to copy PSK to the file.", ex);
        }
    }

    protected String getSnapshotFilepathForDelete(String path, String snapshotName) {
        if (!path.endsWith(snapshotName)) {
            return path + "/*" + snapshotName + "*";
        }
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Snapshot file %s is present in the same name directory %s. Deleting the directory", snapshotName, path));
        }
        return path;
    }

    protected Answer deleteSnapshot(final DeleteCommand cmd) {
        DataTO obj = cmd.getData();
        DataStoreTO dstore = obj.getDataStore();
        if (dstore instanceof NfsTO) {
            NfsTO nfs = (NfsTO)dstore;
            String parent = getRootDir(nfs.getUrl(), _nfsVersion);
            if (!parent.endsWith(File.separator)) {
                parent += File.separator;
            }
            String snapshotPath = obj.getPath();
            if (snapshotPath.startsWith(File.separator)) {
                snapshotPath = snapshotPath.substring(1);
            }
            // check if the passed snapshot path is a directory or not. For ImageCache, path is stored as a directory instead of
            // snapshot file name. If so, since backupSnapshot process has already deleted snapshot in cache, so we just do nothing
            // and return true.
            String fullSnapPath = parent + snapshotPath;
            File snapDir = new File(fullSnapPath);
            if (snapDir.exists() && snapDir.isDirectory()) {
                logger.debug("snapshot path " + snapshotPath + " is a directory, already deleted during backup snapshot, so no need to delete");
                return new Answer(cmd, true, null);
            }
            // passed snapshot path is a snapshot file path, then get snapshot directory first
            int index = snapshotPath.lastIndexOf("/");
            String snapshotName = snapshotPath.substring(index + 1);
            snapshotPath = snapshotPath.substring(0, index);
            String absoluteSnapshotPath = parent + snapshotPath;
            // check if snapshot directory exists
            File snapshotDir = new File(absoluteSnapshotPath);
            String details = null;
            if (!snapshotDir.exists()) {
                details = "snapshot directory " + snapshotDir.getName() + " doesn't exist";
                logger.debug(details);
                return new Answer(cmd, true, details);
            }
            // delete snapshot in the directory if exists
            String lPath = getSnapshotFilepathForDelete(absoluteSnapshotPath, snapshotName);
            String result = deleteLocalFile(lPath);
            if (result != null) {
                details = "failed to delete snapshot " + lPath + " , err=" + result;
                logger.warn(details);
                return new Answer(cmd, false, details);
            }

            // delete the directory if it is empty
            if (snapshotDir.isDirectory() && snapshotDir.list().length == 0 && !snapshotDir.delete()) {
                details = String.format("Unable to delete directory [%s] at path [%s].", snapshotDir.getName(), snapshotPath);
                logger.debug(details);
                return new Answer(cmd, false, details);
            }
            return new Answer(cmd, true, null);
        } else if (dstore instanceof S3TO) {
            final S3TO s3 = (S3TO)dstore;
            final String path = obj.getPath();
            final String bucket = s3.getBucketName();
            try {
                S3Utils.deleteObject(s3, bucket, path);
                return new Answer(cmd, true, String.format("Deleted snapshot %1%s from bucket %2$s.", path, bucket));
            } catch (Exception e) {
                final String errorMessage = String.format("Failed to delete snapshot %1$s from bucket %2$s due to the following error: %3$s", path, bucket, e.getMessage());
                logger.error(errorMessage, e);
                return new Answer(cmd, false, errorMessage);
            }
        } else if (dstore instanceof SwiftTO) {
            SwiftTO swiftTO = (SwiftTO)dstore;
            String path = obj.getPath();
            SwiftUtil.deleteObject(swiftTO, path);

            return new Answer(cmd, true, "Deleted snapshot " + path + " from swift");
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + dstore);
        }

    }

    Map<String, TemplateProp> swiftListTemplate(SwiftTO swift) {
        String[] containers = SwiftUtil.list(swift, "", null);
        if (containers == null) {
            return null;
        }
        Map<String, TemplateProp> tmpltInfos = new HashMap<String, TemplateProp>();
        for (String container : containers) {
            if (container.startsWith("T-")) {
                String[] files = SwiftUtil.list(swift, container, _tmpltpp);
                if (files.length != 1) {
                    continue;
                }
                try {
                    File tempFile = File.createTempFile("template", ".tmp");
                    File tmpFile = SwiftUtil.getObject(swift, tempFile, container + File.separator + _tmpltpp);
                    if (tmpFile == null) {
                        continue;
                    }
                    try (FileReader fr = new FileReader(tmpFile); BufferedReader brf = new BufferedReader(fr);) {
                        String line = null;
                        String uniqName = null;
                        Long size = null;
                        Long physicalSize = null;
                        String name = null;
                        while ((line = brf.readLine()) != null) {
                            if (line.startsWith("uniquename=")) {
                                uniqName = line.split("=")[1];
                            } else if (line.startsWith("size=")) {
                                physicalSize = Long.parseLong(line.split("=")[1]);
                            } else if (line.startsWith("virtualsize=")) {
                                size = Long.parseLong(line.split("=")[1]);
                            } else if (line.startsWith("filename=")) {
                                name = line.split("=")[1];
                            }
                        }

                        //fallback
                        if (size == null) {
                            size = physicalSize;
                        }

                        tempFile.delete();
                        if (uniqName != null) {
                            TemplateProp prop = new TemplateProp(uniqName, container + File.separator + name, size, physicalSize, true, false);
                            tmpltInfos.put(uniqName, prop);
                        }
                    } catch (IOException ex) {
                        logger.debug("swiftListTemplate:Exception:" + ex.getMessage());
                        continue;
                    }
                } catch (IOException e) {
                    logger.debug("Failed to create templ file:" + e.toString());
                    continue;
                } catch (Exception e) {
                    logger.debug("Failed to get properties: " + e.toString());
                    continue;
                }
            }
        }
        return tmpltInfos;
    }

    Map<String, TemplateProp> s3ListTemplate(S3TO s3) {
        String bucket = s3.getBucketName();
        // List the objects in the source directory on S3
        final List<S3ObjectSummary> objectSummaries = S3Utils.listDirectory(s3, bucket, TEMPLATE_ROOT_DIR);
        if (objectSummaries == null) {
            return null;
        }
        Map<String, TemplateProp> tmpltInfos = new HashMap<String, TemplateProp>();
        for (S3ObjectSummary objectSummary : objectSummaries) {
            String key = objectSummary.getKey();
            // String installPath = StringUtils.substringBeforeLast(key,
            // S3Utils.SEPARATOR);
            String uniqueName = determineS3TemplateNameFromKey(key);
            // TODO: isPublic value, where to get?
            TemplateProp tInfo = new TemplateProp(uniqueName, key, objectSummary.getSize(), objectSummary.getSize(), true, false);
            tmpltInfos.put(uniqueName, tInfo);
        }
        return tmpltInfos;

    }

    Map<Long, TemplateProp> s3ListVolume(S3TO s3) {
        String bucket = s3.getBucketName();
        // List the objects in the source directory on S3
        final List<S3ObjectSummary> objectSummaries = S3Utils.listDirectory(s3, bucket, VOLUME_ROOT_DIR);
        if (objectSummaries == null) {
            return null;
        }
        Map<Long, TemplateProp> tmpltInfos = new HashMap<Long, TemplateProp>();
        for (S3ObjectSummary objectSummary : objectSummaries) {
            String key = objectSummary.getKey();
            // String installPath = StringUtils.substringBeforeLast(key,
            // S3Utils.SEPARATOR);
            Long id = determineS3VolumeIdFromKey(key);
            // TODO: how to get volume template name
            TemplateProp tInfo = new TemplateProp(id.toString(), key, objectSummary.getSize(), objectSummary.getSize(), true, false);
            tmpltInfos.put(id, tInfo);
        }
        return tmpltInfos;

    }

    private Answer execute(ListTemplateCommand cmd) {
        if (!_inSystemVM) {
            return new ListTemplateAnswer(null, null);
        }

        DataStoreTO store = cmd.getDataStore();
        if (store instanceof NfsTO) {
            NfsTO nfs = (NfsTO)store;
            String secUrl = nfs.getUrl();
            String root = getRootDir(secUrl, cmd.getNfsVersion());
            Map<String, TemplateProp> templateInfos = _dlMgr.gatherTemplateInfo(root);
            return new ListTemplateAnswer(secUrl, templateInfos);
        } else if (store instanceof SwiftTO) {
            SwiftTO swift = (SwiftTO)store;
            Map<String, TemplateProp> templateInfos = swiftListTemplate(swift);
            return new ListTemplateAnswer(swift.toString(), templateInfos);
        } else if (store instanceof S3TO) {
            S3TO s3 = (S3TO)store;
            Map<String, TemplateProp> templateInfos = s3ListTemplate(s3);
            return new ListTemplateAnswer(s3.getBucketName(), templateInfos);
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + store);
        }
    }

    private Answer execute(ListVolumeCommand cmd) {
        if (!_inSystemVM) {
            return new ListVolumeAnswer(cmd.getSecUrl(), null);
        }
        DataStoreTO store = cmd.getDataStore();
        if (store instanceof NfsTO) {
            String root = getRootDir(cmd.getSecUrl(), _nfsVersion);
            Map<Long, TemplateProp> templateInfos = _dlMgr.gatherVolumeInfo(root);
            return new ListVolumeAnswer(cmd.getSecUrl(), templateInfos);
        } else if (store instanceof S3TO) {
            S3TO s3 = (S3TO)store;
            Map<Long, TemplateProp> templateInfos = s3ListVolume(s3);
            return new ListVolumeAnswer(s3.getBucketName(), templateInfos);
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + store);
        }

    }

    private Answer execute(SecStorageVMSetupCommand cmd) {
        if (!_inSystemVM) {
            return new Answer(cmd, true, null);
        }
        boolean success = true;
        StringBuilder result = new StringBuilder();
        for (String cidr : cmd.getAllowedInternalSites()) {
            if (nfsIps.contains(cidr)) {
                /*
                 * if the internal download ip is the same with secondary
                 * storage ip, adding internal sites will flush ip route to nfs
                 * through storage ip.
                 */
                continue;
            }
            String tmpresult = allowOutgoingOnPrivate(cidr);
            if (tmpresult != null) {
                result.append(", ").append(tmpresult);
                success = false;
            }
        }
        if (success) {
            if (cmd.getCopyPassword() != null && cmd.getCopyUserName() != null) {
                String tmpresult = configureAuth(cmd.getCopyUserName(), cmd.getCopyPassword());
                if (tmpresult != null) {
                    result.append("Failed to configure auth for copy ").append(tmpresult);
                    success = false;
                }
            }
        }
        return new Answer(cmd, success, result.toString());

    }

    private String deleteLocalFile(String fullPath) {
        Script command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("rm -rf " + fullPath);
        String result = command.execute();
        if (result != null) {
            String errMsg = "Failed to delete file " + fullPath + ", err=" + result;
            logger.warn(errMsg);
            return errMsg;
        }
        return null;
    }

    /**
    * allow *only one* setting of an outgoing destination at a time
    *
    * @destCidr the destination network that will be allowed for outgoing traffic.
    * @return any error message that might be helpful or <null> on success or when called anywhere but in the router VM.
    */
    public synchronized String allowOutgoingOnPrivate(String destCidr) {
        if (!_inSystemVM) {
            return null;
        }
        String intf = "eth1";
        String rule =  String.format("-o %s -d %s -p tcp -m state --state NEW -m tcp -j ACCEPT", intf, destCidr);
        String errMsg = String.format("Error in allowing outgoing to %s", destCidr);

        logger.info("Adding rule if required: {}", rule);
        String result = IpTablesHelper.addConditionally(IpTablesHelper.OUTPUT_CHAIN, true, rule, errMsg);
        if (result != null) {
            return result;
        }

        addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, destCidr);

        return null;
    }

    private Answer execute(SecStorageFirewallCfgCommand cmd) {
        if (!_inSystemVM) {
            return new Answer(cmd, true, null);
        }

        List<String> ipList = new ArrayList<String>();

        for (PortConfig pCfg : cmd.getPortConfigs()) {
            if (pCfg.isAdd()) {
                ipList.add(pCfg.getSourceIp());
            }
        }
        boolean success = true;
        String result;
        result = configureIpFirewall(ipList, cmd.getIsAppendAIp());
        if (result != null) {
            success = false;
        }

        return new Answer(cmd, success, result);
    }

    private UploadStatusAnswer execute(UploadStatusCommand cmd) {
        String entityUuid = cmd.getEntityUuid();
        if (uploadEntityStateMap.containsKey(entityUuid)) {
            UploadEntity uploadEntity = uploadEntityStateMap.get(entityUuid);
            if (uploadEntity.getUploadState() == UploadEntity.Status.ERROR) {
                uploadEntityStateMap.remove(entityUuid);
                return new UploadStatusAnswer(cmd, UploadStatus.ERROR, uploadEntity.getErrorMessage());
            } else if (uploadEntity.getUploadState() == UploadEntity.Status.COMPLETED) {
                UploadStatusAnswer answer = new UploadStatusAnswer(cmd, UploadStatus.COMPLETED);
                answer.setVirtualSize(uploadEntity.getVirtualSize());
                answer.setInstallPath(uploadEntity.getTmpltPath());
                answer.setPhysicalSize(uploadEntity.getPhysicalSize());
                answer.setDownloadPercent(100);
                if (uploadEntity.getOvfInformationTO() != null) {
                    answer.setOvfInformationTO(uploadEntity.getOvfInformationTO());
                }
                uploadEntityStateMap.remove(entityUuid);
                return answer;
            } else if (uploadEntity.getUploadState() == UploadEntity.Status.IN_PROGRESS) {
                UploadStatusAnswer answer = new UploadStatusAnswer(cmd, UploadStatus.IN_PROGRESS);
                long downloadedSize = FileUtils.sizeOfDirectory(new File(uploadEntity.getInstallPathPrefix()));
                int downloadPercent = (int)(100 * downloadedSize / uploadEntity.getContentLength());
                answer.setDownloadPercent(Math.min(downloadPercent, 100));
                return answer;
            }
        }
        return new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN);
    }

    protected GetStorageStatsAnswer execute(final GetStorageStatsCommand cmd) {
        DataStoreTO store = cmd.getStore();
        if (store instanceof S3TO || store instanceof SwiftTO) {
            long infinity = Integer.MAX_VALUE;
            return new GetStorageStatsAnswer(cmd, infinity, 0L);
        }

        String rootDir = getRootDir(((NfsTO)store).getUrl(), cmd.getNfsVersion());
        final long usedSize = getUsedSize(rootDir);
        final long totalSize = getTotalSize(rootDir);
        if (usedSize == -1 || totalSize == -1) {
            return new GetStorageStatsAnswer(cmd, "Unable to get storage stats");
        } else {
            return new GetStorageStatsAnswer(cmd, totalSize, usedSize);
        }
    }

    protected Answer execute(final DeleteCommand cmd) {
        DataTO obj = cmd.getData();
        DataObjectType objType = obj.getObjectType();
        if (obj.getPath() == null) {
            // account for those fake entries for NFS migration to object store
            return new Answer(cmd, true, "Object with null install path does not exist on image store , no need to delete");
        }
        switch (objType) {
        case TEMPLATE:
            return deleteTemplate(cmd);
        case VOLUME:
            return deleteVolume(cmd);
        case SNAPSHOT:
            return deleteSnapshot(cmd);
        }
        return Answer.createUnsupportedCommandAnswer(cmd);
    }

    protected Answer deleteTemplate(DeleteCommand cmd) {
        DataTO obj = cmd.getData();
        DataStoreTO dstore = obj.getDataStore();
        if (dstore instanceof NfsTO) {
            NfsTO nfs = (NfsTO)dstore;
            String relativeTemplatePath = obj.getPath();
            String parent = getRootDir(nfs.getUrl(), _nfsVersion);

            if (relativeTemplatePath.startsWith(File.separator)) {
                relativeTemplatePath = relativeTemplatePath.substring(1);
            }

            if (!parent.endsWith(File.separator)) {
                parent += File.separator;
            }
            String absoluteTemplatePath = parent + relativeTemplatePath;
            File tmpltPath = new File(absoluteTemplatePath);
            File tmpltParent = null;
            if (tmpltPath.exists() && tmpltPath.isDirectory()) {
                tmpltParent = tmpltPath;
            } else if (absoluteTemplatePath.endsWith(File.separator + obj.getId())) {
                // the path ends with <account id>/<template id>, if upload fails
                tmpltParent = tmpltPath;
            } else {
                tmpltParent = tmpltPath.getParentFile();
            }

            String details = null;
            if (!tmpltParent.exists()) {
                details = "template parent directory " + tmpltParent.getName() + " doesn't exist";
                logger.debug(details);
                return new Answer(cmd, true, details);
            }
            File[] tmpltFiles = tmpltParent.listFiles();
            if (tmpltFiles == null || tmpltFiles.length == 0) {
                details = "No files under template parent directory " + tmpltParent.getName();
                logger.debug(details);
            } else {
                boolean found = false;
                for (File f : tmpltFiles) {
                    if (!found && f.getName().equals(_tmpltpp)) {
                        found = true;
                    }

                    // KVM HA monitor makes a mess in the templates with its
                    // heartbeat tests
                    // Don't let this stop us from cleaning up the template
                    if (f.isDirectory() && f.getName().equals("KVMHA")) {
                        logger.debug("Deleting KVMHA directory contents from template location");
                        File[] haFiles = f.listFiles();
                        for (File haFile : haFiles) {
                            haFile.delete();
                        }
                    }

                    if (!f.delete()) {
                        return new Answer(cmd, false, "Unable to delete file " + f.getName() + " under Template path " + relativeTemplatePath);
                    }
                }

                if (!found) {
                    details = "Can not find template.properties under " + tmpltParent.getName();
                    logger.debug(details);
                }
            }
            if (!tmpltParent.delete()) {
                details = "Unable to delete directory " + tmpltParent.getName() + " under Template path " + relativeTemplatePath;
                logger.debug(details);
                return new Answer(cmd, false, details);
            }
            return new Answer(cmd, true, null);
        } else if (dstore instanceof S3TO) {
            final S3TO s3 = (S3TO)dstore;
            final String path = obj.getPath();
            final String bucket = s3.getBucketName();
            try {
                S3Utils.deleteDirectory(s3, bucket, path);
                return new Answer(cmd, true, String.format("Deleted template %1$s from bucket %2$s.", path, bucket));
            } catch (Exception e) {
                final String errorMessage = String.format("Failed to delete template %1$s from bucket %2$s due to the following error: %3$s", path, bucket, e.getMessage());
                logger.error(errorMessage, e);
                return new Answer(cmd, false, errorMessage);
            }
        } else if (dstore instanceof SwiftTO) {
            SwiftTO swift = (SwiftTO)dstore;
            String container = "T-" + obj.getId();
            String object = "";

            try {
                String result = swiftDelete(swift, container, object);
                if (result != null) {
                    String errMsg = "failed to delete object " + container + "/" + object + " , err=" + result;
                    logger.warn(errMsg);
                    return new Answer(cmd, false, errMsg);
                }
                return new Answer(cmd, true, "success");
            } catch (Exception e) {
                String errMsg = cmd + " Command failed due to " + e.toString();
                logger.warn(errMsg, e);
                return new Answer(cmd, false, errMsg);
            }
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + dstore);
        }
    }

    protected Answer deleteVolume(final DeleteCommand cmd) {
        DataTO obj = cmd.getData();
        DataStoreTO dstore = obj.getDataStore();
        if (dstore instanceof NfsTO) {
            NfsTO nfs = (NfsTO)dstore;
            String relativeVolumePath = obj.getPath();
            String parent = getRootDir(nfs.getUrl(), _nfsVersion);

            if (relativeVolumePath.startsWith(File.separator)) {
                relativeVolumePath = relativeVolumePath.substring(1);
            }

            if (!parent.endsWith(File.separator)) {
                parent += File.separator;
            }
            String absoluteVolumePath = parent + relativeVolumePath;
            File volPath = new File(absoluteVolumePath);
            File tmpltParent = null;
            if (volPath.exists() && volPath.isDirectory()) {
                // for vmware, absoluteVolumePath represents a directory where volume files are located.
                tmpltParent = volPath;
            } else if (absoluteVolumePath.endsWith(File.separator + obj.getId())) {
                // the path ends with <account id>/<volume id>, if upload fails
                tmpltParent = volPath;
            } else {
                // for other hypervisors, the volume .vhd or .qcow2 file path is passed
                tmpltParent = new File(absoluteVolumePath).getParentFile();
            }
            String details = null;
            if (!tmpltParent.exists()) {
                details = "volume parent directory " + tmpltParent.getName() + " doesn't exist";
                logger.debug(details);
                return new Answer(cmd, true, details);
            }
            File[] tmpltFiles = tmpltParent.listFiles();
            if (tmpltFiles == null || tmpltFiles.length == 0) {
                details = "No files under volume parent directory " + tmpltParent.getName();
                logger.debug(details);
            } else {
                boolean found = false;
                for (File f : tmpltFiles) {
                    if (!found && f.getName().equals("volume.properties")) {
                        found = true;
                    }

                    // KVM HA monitor makes a mess in the templates with its
                    // heartbeat tests
                    // Don't let this stop us from cleaning up the template
                    if (f.isDirectory() && f.getName().equals("KVMHA")) {
                        logger.debug("Deleting KVMHA directory contents from template location");
                        File[] haFiles = f.listFiles();
                        for (File haFile : haFiles) {
                            haFile.delete();
                        }
                    }

                    if (!f.delete()) {
                        return new Answer(cmd, false, "Unable to delete file " + f.getName() + " under Volume path " + tmpltParent.getPath());
                    }
                }
                if (!found) {
                    details = "Can not find volume.properties under " + tmpltParent.getName();
                    logger.debug(details);
                }
            }
            if (!tmpltParent.delete()) {
                details = "Unable to delete directory " + tmpltParent.getName() + " under Volume path " + tmpltParent.getPath();
                logger.debug(details);
                return new Answer(cmd, false, details);
            }
            return new Answer(cmd, true, null);
        } else if (dstore instanceof S3TO) {
            final S3TO s3 = (S3TO)dstore;
            final String path = obj.getPath();
            final String bucket = s3.getBucketName();
            try {
                S3Utils.deleteDirectory(s3, bucket, path);
                return new Answer(cmd, true, String.format("Deleted volume %1%s from bucket %2$s.", path, bucket));
            } catch (Exception e) {
                final String errorMessage = String.format("Failed to delete volume %1$s from bucket %2$s due to the following error: %3$s", path, bucket, e.getMessage());
                logger.error(errorMessage, e);
                return new Answer(cmd, false, errorMessage);
            }
        } else if (dstore instanceof SwiftTO) {
            Long volumeId = obj.getId();
            String path = obj.getPath();
            String filename = StringUtils.substringAfterLast(path, "/"); // assuming
            // that
            // the
            // filename
            // is
            // the
            // last
            // section
            // in
            // the
            // path
            String result = swiftDelete((SwiftTO)dstore, "V-" + volumeId.toString(), filename);
            if (result != null) {
                String errMsg = "failed to delete volume " + filename + " , err=" + result;
                logger.warn(errMsg);
                return new Answer(cmd, false, errMsg);
            }
            return new Answer(cmd, true, "Deleted volume " + path + " from swift");
        } else {
            return new Answer(cmd, false, "Unsupported image data store: " + dstore);
        }

    }

    private String getDir(String secUrl, String nfsVersion) {
        try {
            URI uri = new URI(secUrl);
            String dir = mountUri(uri, nfsVersion);
            return _parent + "/" + dir;
        } catch (Exception e) {
            String msg = "GetRootDir for " + secUrl + " failed due to " + e.toString();
            logger.error(msg, e);
            throw new CloudRuntimeException(msg);
        }
    }

    @Override
    synchronized public String getRootDir(String secUrl, String nfsVersion) {
        if (!_inSystemVM) {
            return _parent;
        }
        try {
            logger.debug(String.format("Trying to get root directory from secondary storage URL [%s] using NFS version [%s].", secUrl, nfsVersion));
            URI uri = new URI(secUrl);
            String dir = mountUri(uri, nfsVersion);
            return _parent + "/" + dir;
        } catch (Exception e) {
            String msg = String.format("Failed to get root directory from secondary storage URL [%s], using NFS version [%s], due to [%s].", secUrl, nfsVersion, e.getMessage());
            throw new CloudRuntimeException(msg, e);
        }
    }

    protected long getUsedSize(String rootDir) {
        return _storage.getUsedSpace(rootDir);
    }

    protected long getTotalSize(String rootDir) {
        return _storage.getTotalSpace(rootDir);
    }

    protected long convertFilesystemSize(final String size) {
        if (size == null || size.isEmpty()) {
            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 {
            assert (false) : "Well, I have no idea what this is: " + size;
        }

        return (long)(Double.parseDouble(size.substring(0, size.length() - 1)) * multiplier);
    }

    @Override
    public Type getType() {
        if (SecondaryStorageVm.Role.templateProcessor.toString().equals(_role)) {
            return Host.Type.SecondaryStorage;
        }

        return Host.Type.SecondaryStorageCmdExecutor;
    }

    @Override
    public PingCommand getCurrentStatus(final long id) {
        return new PingStorageCommand(Host.Type.Storage, id, new HashMap<String, Boolean>());
    }

    protected void configureStorageNetwork(Map<String, Object> params) {
        _storageIp = MapUtils.getString(params, "storageip");
        _storageNetmask = (String) params.get("storagenetmask");
        _storageGateway = (String) params.get("storagegateway");
        if (_storageIp == null && _inSystemVM && _eth1ip != null) {
            String eth1Gateway = ObjectUtils.firstNonNull(_localgw, MapUtils.getString(params, "localgw"));
            logger.info("Storage network not configured, using management network[ip: {}, netmask: {}, gateway: {}] for storage traffic",
                    _eth1ip, _eth1mask, eth1Gateway);
            _storageIp = _eth1ip;
            _storageNetmask = _eth1mask;
            _storageGateway = eth1Gateway;
        }
    }

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        _eth1ip = (String)params.get("eth1ip");
        _eth1mask = (String)params.get("eth1mask");
        if (_eth1ip != null) { // can only happen inside service vm
            params.put("private.network.device", "eth1");
        } else {
            logger.warn("eth1ip parameter has not been configured, assuming that we are not inside a system vm");
        }
        String eth2ip = (String)params.get("eth2ip");
        if (eth2ip != null) {
            params.put("public.network.device", "eth2");
        }
        _publicIp = (String)params.get("eth2ip");
        _hostname = (String)params.get("name");

        String inSystemVM = (String)params.get("secondary.storage.vm");
        if (inSystemVM == null || "true".equalsIgnoreCase(inSystemVM)) {
            logger.debug("conf secondary.storage.vm is true, act as if executing in SSVM");
            _inSystemVM = true;
        }

        configureStorageNetwork(params);
        if (_storageIp == null && _inSystemVM) {
            logger.warn("No storageip in /proc/cmdline, something wrong! Even fallback to management network did not resolve storage IP.");
        }
        super.configure(name, params);

        _params = params;
        String value = (String)params.get("scripts.timeout");
        _timeout = NumbersUtil.parseInt(value, 1440) * 1000;

        _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey);
        configureStorageLayerClass(params);

        if (_inSystemVM) {
            _storage.mkdirs(_parent);
        }

        _configSslScr = Script.findScript(getDefaultScriptsDir(), "config_ssl.sh");
        if (_configSslScr != null) {
            logger.info("config_ssl.sh found in " + _configSslScr);
        }

        _configAuthScr = Script.findScript(getDefaultScriptsDir(), "config_auth.sh");
        if (_configAuthScr != null) {
            logger.info("config_auth.sh found in " + _configAuthScr);
        }

        _configIpFirewallScr = Script.findScript(getDefaultScriptsDir(), "ipfirewall.sh");
        if (_configIpFirewallScr != null) {
            logger.info("_configIpFirewallScr found in " + _configIpFirewallScr);
        }

        createTemplateFromSnapshotXenScript = Script.findScript(getDefaultScriptsDir(), "create_privatetemplate_from_snapshot_xen.sh");
        if (createTemplateFromSnapshotXenScript == null) {
            throw new ConfigurationException("create_privatetemplate_from_snapshot_xen.sh not found in " + getDefaultScriptsDir());
        }

        _role = (String)params.get("role");
        if (_role == null) {
            _role = SecondaryStorageVm.Role.templateProcessor.toString();
        }
        logger.info("Secondary storage runs in role " + _role);

        _guid = (String)params.get("guid");
        if (_guid == null) {
            throw new ConfigurationException("Unable to find the guid");
        }

        _dc = (String)params.get("zone");
        if (_dc == null) {
            throw new ConfigurationException("Unable to find the zone");
        }
        _pod = (String)params.get("pod");

        _instance = (String)params.get("instance");

        if (!_inSystemVM) {
            _parent = (String)params.get("mount.path");
        }

        if (_inSystemVM) {
            _localgw = (String)params.get("localgw");
            if (_localgw != null) { // can only happen inside service vm
                String mgmtHosts = (String)params.get("host");
                for (final String mgmtHost : mgmtHosts.split(",")) {
                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
                }

                String internalDns1 = (String)params.get("internaldns1");
                if (internalDns1 == null) {
                    logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");
                } else {
                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, internalDns1);
                }

                String internalDns2 = (String)params.get("internaldns2");
                if (internalDns2 != null) {
                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, internalDns2);
                }

            }

            startAdditionalServices();
            _params.put("install.numthreads", "50");
            _params.put("secondary.storage.vm", "true");
            _nfsVersion = retrieveNfsVersionFromParams(params);
        }

        try {
            _params.put(StorageLayer.InstanceConfigKey, _storage);
            _dlMgr = new DownloadManagerImpl();
            _dlMgr.configure("DownloadManager", _params);
            _upldMgr = new UploadManagerImpl();
            _upldMgr.configure("UploadManager", params);
        } catch (ConfigurationException e) {
            logger.warn("Caught problem while configuring DownloadManager", e);
            return false;
        }
        return true;
    }

    protected void configureStorageLayerClass(Map<String, Object> params) throws ConfigurationException {
        String value;
        if (_storage == null) {
            value = (String)params.get(StorageLayer.ClassConfigKey);
            if (value == null) {
                value = "com.cloud.storage.JavaStorageLayer";
            }

            try {
                Class<?> clazz = Class.forName(value);
                _storage = (StorageLayer)clazz.newInstance();
                _storage.configure("StorageLayer", params);
            } catch (ClassNotFoundException e) {
                throw new ConfigurationException("Unable to find class " + value);
            } catch (InstantiationException e) {
                throw new ConfigurationException("Unable to find class " + value);
            } catch (IllegalAccessException e) {
                throw new ConfigurationException("Unable to find class " + value);
            }
        }
    }

    private void startAdditionalServices() {
        if (!_inSystemVM) {
            return;
        }
        Script command = new Script("/bin/systemctl", logger);
        command.add("restart");
        command.add("ssh");
        String result = command.execute();
        if (result != null) {
            logger.warn("Error in starting sshd service err=" + result);
        }
        String rule = "-i eth1 -p tcp -m state --state NEW -m tcp --dport 3922 -j ACCEPT";
        IpTablesHelper.addConditionally(IpTablesHelper.INPUT_CHAIN, true, rule, "Error in opening up ssh port");
    }

    private void addRouteToInternalIpOrCidr(String localgw, String eth1ip, String eth1mask, String destIpOrCidr) {
        if (!_inSystemVM) {
            return;
        }
        logger.debug("addRouteToInternalIp: localgw=" + localgw + ", eth1ip=" + eth1ip + ", eth1mask=" + eth1mask + ",destIp=" + destIpOrCidr);
        if (destIpOrCidr == null) {
            logger.debug("addRouteToInternalIp: destIp is null");
            return;
        }
        if (!NetUtils.isValidIp4(destIpOrCidr) && !NetUtils.isValidIp4Cidr(destIpOrCidr)) {
            logger.warn(" destIp is not a valid ip address or cidr destIp=" + destIpOrCidr);
            return;
        }
        boolean inSameSubnet = false;
        if (NetUtils.isValidIp4(destIpOrCidr)) {
            if (eth1ip != null && eth1mask != null) {
                inSameSubnet = NetUtils.sameSubnet(eth1ip, destIpOrCidr, eth1mask);
            } else {
                logger.warn("addRouteToInternalIp: unable to determine same subnet: _eth1ip=" + eth1ip + ", dest ip=" + destIpOrCidr + ", _eth1mask=" + eth1mask);
            }
        } else {
            inSameSubnet = NetUtils.isNetworkAWithinNetworkB(destIpOrCidr, NetUtils.ipAndNetMaskToCidr(eth1ip, eth1mask));
        }
        if (inSameSubnet) {
            logger.debug("addRouteToInternalIp: dest ip " + destIpOrCidr + " is in the same subnet as eth1 ip " + eth1ip);
            return;
        }
        Script command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("ip route delete " + destIpOrCidr);
        command.execute();
        command = new Script("/bin/bash", logger);
        command.add("-c");
        command.add("ip route add " + destIpOrCidr + " via " + localgw);
        String result = command.execute();
        if (result != null) {
            logger.warn("Error in configuring route to internal ip err=" + result);
        } else {
            logger.debug("addRouteToInternalIp: added route to internal ip=" + destIpOrCidr + " via " + localgw);
        }
    }

    private void configureSSL() {
        if (!_inSystemVM) {
            return;
        }
        Script command = new Script(_configSslScr);
        command.add("-i", _publicIp);
        command.add("-h", _hostname);
        String result = command.execute();
        if (result != null) {
            logger.warn("Unable to configure httpd to use ssl");
        }
    }

    private void configureSSL(String prvkeyPath, String prvCertPath, String certChainPath, String rootCACert) {
        if (!_inSystemVM) {
            return;
        }
        Script command = new Script(_configSslScr);
        command.add("-i", _publicIp);
        command.add("-h", _hostname);
        command.add("-k", prvkeyPath);
        command.add("-p", prvCertPath);
        if (certChainPath != null) {
            command.add("-t", certChainPath);
        }
        if (rootCACert != null) {
            command.add("-u", rootCACert);
        }
        String result = command.execute();
        if (result != null) {
            logger.warn("Unable to configure httpd to use ssl");
        }
    }

    private String configureAuth(String user, String passwd) {
        Script command = new Script(_configAuthScr);
        command.add(user);
        command.add(passwd);
        String result = command.execute();
        if (result != null) {
            logger.warn("Unable to configure httpd to use auth");
        }
        return result;
    }

    private String configureIpFirewall(List<String> ipList, boolean isAppend) {
        Script command = new Script(_configIpFirewallScr);
        command.add(String.valueOf(isAppend));
        for (String ip : ipList) {
            command.add(ip);
        }

        String result = command.execute();
        if (result != null) {
            logger.warn("Unable to configure firewall for command : " + command);
        }
        return result;
    }

    /**
     * Mount remote device named on local file system on subfolder of _parent
     * field.
     * <p>
     *
     * Supported schemes are "nfs" and "cifs".
     * <p>
     *
     * CIFS parameters are documented with mount.cifs at
     * http://linux.die.net/man/8/mount.cifs
     * For simplicity, when a URI is used to specify a CIFS share,
     * options such as domain,user,password are passed as query parameters.
     *
     * @param uri
     *            crresponding to the remote device. Will throw for unsupported
     *            scheme.
     * @param nfsVersion NFS version to use in mount command
     * @return name of folder in _parent that device was mounted.
     * @throws UnknownHostException
     */
    protected String mountUri(URI uri, String nfsVersion) throws UnknownHostException {
        String uriHostIp = getUriHostIp(uri);
        String nfsPath = uriHostIp + ":" + uri.getPath();

        // Single means of calculating mount directory regardless of scheme
        String dir = UUID.nameUUIDFromBytes(nfsPath.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())).toString();
        String localRootPath = _parent + "/" + dir;

        // remote device syntax varies by scheme.
        String remoteDevice;
        if (uri.getScheme().equals("cifs")) {
            remoteDevice = "//" + uriHostIp + uri.getPath();
            logger.debug("Mounting device with cifs-style path of " + remoteDevice);
        } else {
            remoteDevice = nfsPath;
            logger.debug("Mounting device with nfs-style path of " + remoteDevice);
        }
        mount(localRootPath, remoteDevice, uri, nfsVersion);
        return dir;
    }

    protected void mount(String localRootPath, String remoteDevice, URI uri, String nfsVersion) {
        logger.debug("mount " + uri.toString() + " on " + localRootPath + ((nfsVersion != null) ? " nfsVersion=" + nfsVersion : ""));
        ensureLocalRootPathExists(localRootPath, uri);

        if (mountExists(localRootPath, uri)) {
            return;
        }

        attemptMount(localRootPath, remoteDevice, uri, nfsVersion);

        // XXX: Adding the check for creation of snapshots dir here. Might have
        // to move it somewhere more logical later.
        checkForSnapshotsDir(localRootPath);
        checkForVolumesDir(localRootPath);
    }

    protected void attemptMount(String localRootPath, String remoteDevice, URI uri, String nfsVersion) {
        String result;
        logger.debug("Make cmdline call to mount " + remoteDevice + " at " + localRootPath + " based on uri " + uri + ((nfsVersion != null) ? " nfsVersion=" + nfsVersion : ""));
        Script command = new Script(!_inSystemVM, "mount", _timeout, logger);

        String scheme = uri.getScheme().toLowerCase();
        command.add("-t", scheme);

        if (scheme.equals("nfs")) {
            if ("Mac OS X".equalsIgnoreCase(System.getProperty("os.name"))) {
                // See http://wiki.qnap.com/wiki/Mounting_an_NFS_share_from_OS_X
                command.add("-o", "resvport");
            }
            if (_inSystemVM) {
                command.add("-o", "soft,timeo=133,retrans=2147483647,tcp,acdirmax=0,acdirmin=0" + ((nfsVersion != null) ? ",vers=" + nfsVersion : ""));
            }
        } else if (scheme.equals("cifs")) {
            String extraOpts = parseCifsMountOptions(uri);

            // nfs acdirmax / acdirmin correspoonds to CIFS actimeo (see
            // http://linux.die.net/man/8/mount.cifs)
            // no equivalent to nfs timeo, retrans or tcp in CIFS
            // todo: allow security mode to be set.
            command.add("-o", extraOpts + "soft,actimeo=0");
        } else {
            String errMsg = "Unsupported storage device scheme " + scheme + " in uri " + uri.toString();
            logger.error(errMsg);
            throw new CloudRuntimeException(errMsg);
        }

        command.add(remoteDevice);
        command.add(localRootPath);
        result = command.execute();
        if (result != null) {
            // Fedora Core 12 errors out with any -o option executed from java
            String errMsg = "Unable to mount " + remoteDevice + " at " + localRootPath + " due to " + result;
            logger.error(errMsg);
            File file = new File(localRootPath);
            if (file.exists()) {
                file.delete();
            }
            throw new CloudRuntimeException(errMsg);
        }
        logger.debug("Successfully mounted " + remoteDevice + " at " + localRootPath);
    }

    protected String parseCifsMountOptions(URI uri) {
        List<NameValuePair> args = URLEncodedUtils.parse(uri, "UTF-8");
        boolean foundUser = false;
        boolean foundPswd = false;
        StringBuilder extraOpts = new StringBuilder();
        for (NameValuePair nvp : args) {
            String name = nvp.getName();
            if (name.equals("user")) {
                foundUser = true;
                logger.debug("foundUser is" + foundUser);
            } else if (name.equals("password")) {
                foundPswd = true;
                logger.debug("password is present in uri");
            }

            extraOpts.append(name + "=" + nvp.getValue() + ",");
        }

        String extraOptions = extraOpts.toString();
        logger.debug("extraOpts now {}", ()->StringUtils.cleanString(extraOptions));

        if (!foundUser || !foundPswd) {
            String errMsg = "Missing user and password from URI. Make sure they" + "are in the query string and separated by '&'.  E.g. "
                    + "cifs://example.com/some_share?user=foo&password=bar";
            logger.error(errMsg);
            throw new CloudRuntimeException(errMsg);
        }
        return extraOptions;
    }

    protected boolean mountExists(String localRootPath, URI uri) {
        Script script = null;
        script = new Script(!_inSystemVM, "mount", _timeout, logger);

        List<String> res = new ArrayList<String>();
        PathParser parser = new PathParser(localRootPath);
        script.execute(parser);
        res.addAll(parser.getPaths());
        for (String s : res) {
            if (s.contains(localRootPath)) {
                logger.debug("Some device already mounted at " + localRootPath + ", no need to mount " + uri.toString());
                return true;
            }
        }
        return false;
    }

    protected void ensureLocalRootPathExists(String localRootPath, URI uri) {
        logger.debug("making available " + localRootPath + " on " + uri.toString());
        File file = new File(localRootPath);
        logger.debug("local folder for mount will be " + file.getPath());
        if (!file.exists()) {
            logger.debug("create mount point: " + file.getPath());
            _storage.mkdir(file.getPath());

            // Need to check after mkdir to allow O/S to complete operation
            if (!file.exists()) {
                String errMsg = "Unable to create local folder for: " + localRootPath + " in order to mount " + uri.toString();
                logger.error(errMsg);
                throw new CloudRuntimeException(errMsg);
            }
        }
    }

    protected String getUriHostIp(URI uri) throws UnknownHostException {
        String nfsHost = uri.getHost();
        InetAddress nfsHostAddr = InetAddress.getByName(nfsHost);
        String nfsHostIp = nfsHostAddr.getHostAddress();
        logger.info("Determined host " + nfsHost + " corresponds to IP " + nfsHostIp);
        return nfsHostIp;
    }

    @Override
    public boolean start() {
        return true;
    }

    @Override
    public boolean stop() {
        return true;
    }

    @Override
    public StartupCommand[] initialize() {

        final StartupSecondaryStorageCommand cmd = new StartupSecondaryStorageCommand();
        fillNetworkInformation(cmd);
        if (_publicIp != null) {
            cmd.setPublicIpAddress(_publicIp);
        }

        if (_inSystemVM) {
            Script command = new Script("/bin/bash", logger);
            command.add("-c");
            command.add("ln -sf " + _parent + " /var/www/html/copy");
            String result = command.execute();
            if (result != null) {
                logger.warn("Error in linking  err=" + result);
                return null;
            }
        }
        return new StartupCommand[] {cmd};
    }

    protected boolean checkForSnapshotsDir(String mountPoint) {
        String snapshotsDirLocation = mountPoint + File.separator + "snapshots";
        return createDir("snapshots", snapshotsDirLocation, mountPoint);
    }

    protected boolean checkForVolumesDir(String mountPoint) {
        String volumesDirLocation = mountPoint + "/" + "volumes";
        return createDir("volumes", volumesDirLocation, mountPoint);
    }

    protected boolean createDir(String dirName, String dirLocation, String mountPoint) {
        boolean dirExists = false;

        File dir = new File(dirLocation);
        if (dir.exists()) {
            if (dir.isDirectory()) {
                logger.debug(dirName + " already exists on secondary storage, and is mounted at " + mountPoint);
                dirExists = true;
            } else {
                if (dir.delete() && _storage.mkdir(dirLocation)) {
                    dirExists = true;
                }
            }
        } else if (_storage.mkdir(dirLocation)) {
            dirExists = true;
        }

        if (dirExists) {
            logger.info(dirName + " directory created/exists on Secondary Storage.");
        } else {
            logger.info(dirName + " directory does not exist on Secondary Storage.");
        }

        return dirExists;
    }

    @Override
    protected String getDefaultScriptsDir() {
        return "./scripts/storage/secondary";
    }

    @Override
    public void setName(String name) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setConfigParams(Map<String, Object> params) {
        // TODO Auto-generated method stub

    }

    @Override
    public Map<String, Object> getConfigParams() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getRunLevel() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void setRunLevel(int level) {
        // TODO Auto-generated method stub

    }

    @Override
    public void fillNetworkInformation(final StartupCommand cmd) {
        final String dummyMac = "00:06:0A:0B:0C:0D";
        final String dummyNetmask = "255.255.255.0";
        if (!_inSystemVM) {
            cmd.setPrivateIpAddress(_eth1ip);
            cmd.setPrivateMacAddress(dummyMac);
            cmd.setPrivateNetmask(dummyNetmask);
            cmd.setPublicIpAddress(_publicIp);
            cmd.setPublicMacAddress(dummyMac);
            cmd.setPublicNetmask(dummyNetmask);
            cmd.setName(_hostname);
        } else {
            super.fillNetworkInformation(cmd);
        }
    }

    private String getScriptLocation(UploadEntity.ResourceType resourceType) {

        String scriptsDir = (String)_params.get("template.scripts.dir");
        if (scriptsDir == null) {
            scriptsDir = "scripts/storage/secondary";
        }
        String scriptname = null;
        if (resourceType == UploadEntity.ResourceType.VOLUME) {
            scriptname = "createvolume.sh";
        } else if (resourceType == UploadEntity.ResourceType.TEMPLATE) {
            scriptname = "createtmplt.sh";
        } else {
            throw new InvalidParameterValueException("cannot find script for resource type: " + resourceType);
        }
        return Script.findScript(scriptsDir, scriptname);
    }

    public UploadEntity createUploadEntity(String uuid, String metadata, long contentLength) {
        TemplateOrVolumePostUploadCommand cmd = getTemplateOrVolumePostUploadCmd(metadata);
        UploadEntity uploadEntity = null;
        if (cmd == null) {
            String errorMessage = "unable decode and deserialize metadata.";
            updateStateMapWithError(uuid, errorMessage);
            throw new InvalidParameterValueException(errorMessage);
        } else {
            uuid = cmd.getEntityUUID();
            processTimeout = cmd.getProcessTimeout();
            if (isOneTimePostUrlUsed(cmd)) {
                uploadEntity = uploadEntityStateMap.get(uuid);
                StringBuilder errorMessage = new StringBuilder("The one time post url is already used");
                if (uploadEntity != null) {
                    errorMessage.append(" and the upload is in ").append(uploadEntity.getUploadState()).append(" state.");
                }
                throw new InvalidParameterValueException(errorMessage.toString());
            }
            int maxSizeInGB = Integer.parseInt(cmd.getMaxUploadSize());
            int contentLengthInGB = getSizeInGB(contentLength);
            if (contentLengthInGB > maxSizeInGB) {
                String errorMessage = "Maximum file upload size exceeded. Content Length received: " + contentLengthInGB + "GB. Maximum allowed size: " + maxSizeInGB + "GB.";
                updateStateMapWithError(uuid, errorMessage);
                throw new InvalidParameterValueException(errorMessage);
            }
            checkSecondaryStorageResourceLimit(cmd, contentLengthInGB);
            try {
                String absolutePath = cmd.getAbsolutePath();
                uploadEntity = new UploadEntity(uuid, cmd.getEntityId(), UploadEntity.Status.IN_PROGRESS, cmd.getName(), absolutePath);
                uploadEntity.setMetaDataPopulated(true);
                uploadEntity.setResourceType(UploadEntity.ResourceType.valueOf(cmd.getType()));
                uploadEntity.setProcessTimeout(processTimeout);
                uploadEntity.setFormat(Storage.ImageFormat.valueOf(cmd.getImageFormat()));
                //relative path with out ssvm mount info.
                uploadEntity.setTemplatePath(absolutePath);
                String dataStoreUrl = cmd.getDataTo();
                String installPathPrefix = this.getRootDir(dataStoreUrl, cmd.getNfsVersion()) + File.separator + absolutePath;
                uploadEntity.setInstallPathPrefix(installPathPrefix);
                uploadEntity.setHvm(cmd.getRequiresHvm());
                uploadEntity.setChksum(cmd.getChecksum());
                uploadEntity.setMaxSizeInGB(maxSizeInGB);
                uploadEntity.setDescription(cmd.getDescription());
                uploadEntity.setContentLength(contentLength);
                // create a install dir
                if (!_storage.exists(installPathPrefix)) {
                    _storage.mkdir(installPathPrefix);
                }
                uploadEntityStateMap.put(uuid, uploadEntity);
            } catch (Exception e) {
                //upload entity will be null incase an exception occurs and the handler will not proceed.
                logger.error("exception occurred while creating upload entity ", e);
                updateStateMapWithError(uuid, e.getMessage());
            }
        }
        return uploadEntity;
    }

    private synchronized void checkSecondaryStorageResourceLimit(TemplateOrVolumePostUploadCommand cmd, int contentLengthInGB) {
        String rootDir = this.getRootDir(cmd.getDataTo(), cmd.getNfsVersion()) + File.separator;
        long accountId = cmd.getAccountId();

        long accountTemplateDirSize = 0;
        File accountTemplateDir = new File(rootDir + getTemplatePathForAccount(accountId));
        if (accountTemplateDir.exists()) {
            accountTemplateDirSize = FileUtils.sizeOfDirectory(accountTemplateDir);
        }
        long accountVolumeDirSize = 0;
        File accountVolumeDir = new File(rootDir + getVolumePathForAccount(accountId));
        if (accountVolumeDir.exists()) {
            accountVolumeDirSize = FileUtils.sizeOfDirectory(accountVolumeDir);
        }
        long accountSnapshotDirSize = 0;
        File accountSnapshotDir = new File(rootDir + getSnapshotPathForAccount(accountId));
        if (accountSnapshotDir.exists()) {
            accountSnapshotDirSize = FileUtils.sizeOfDirectory(accountSnapshotDir);
        }
        logger.debug(
                "accountTemplateDirSize: " + accountTemplateDirSize + " accountSnapshotDirSize: " + accountSnapshotDirSize + " accountVolumeDirSize: " + accountVolumeDirSize);

        int accountDirSizeInGB = getSizeInGB(accountTemplateDirSize + accountSnapshotDirSize + accountVolumeDirSize);
        long defaultMaxSecondaryStorageInGB = cmd.getDefaultMaxSecondaryStorageInGB();

        if (defaultMaxSecondaryStorageInGB != Resource.RESOURCE_UNLIMITED && (accountDirSizeInGB + contentLengthInGB) > defaultMaxSecondaryStorageInGB) {
            logger.error("accountDirSizeInGb: " + accountDirSizeInGB + " defaultMaxSecondaryStorageInGB: " + defaultMaxSecondaryStorageInGB + " contentLengthInGB:"
                    + contentLengthInGB); // extra attention
            String errorMessage = "Maximum number of resources of type secondary_storage for account/project has exceeded";
            updateStateMapWithError(cmd.getEntityUUID(), errorMessage);
            throw new InvalidParameterValueException(errorMessage);
        }
    }

    private String getVolumePathForAccount(long accountId) {
        return TemplateConstants.DEFAULT_VOLUME_ROOT_DIR + "/" + accountId;
    }

    private String getTemplatePathForAccount(long accountId) {
        return TemplateConstants.DEFAULT_TMPLT_ROOT_DIR + "/" + TemplateConstants.DEFAULT_TMPLT_FIRST_LEVEL_DIR + accountId;
    }

    private String getSnapshotPathForAccount(long accountId) {
        return TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR + "/" + accountId;
    }

    private boolean isOneTimePostUrlUsed(TemplateOrVolumePostUploadCommand cmd) {
        String uuid = cmd.getEntityUUID();
        String uploadPath = this.getRootDir(cmd.getDataTo(), cmd.getNfsVersion()) + File.separator + cmd.getAbsolutePath();
        return uploadEntityStateMap.containsKey(uuid) || new File(uploadPath).exists();
    }

    private int getSizeInGB(long sizeInBytes) {
        return (int)Math.ceil(sizeInBytes * 1.0d / (1024 * 1024 * 1024));
    }

    public String postUpload(String uuid, String filename, long processTimeout) {
        UploadEntity uploadEntity = uploadEntityStateMap.get(uuid);
        int installTimeoutPerGig = 180 * 60 * 1000;

        String resourcePath = uploadEntity.getInstallPathPrefix();
        String finalResourcePath = uploadEntity.getTmpltPath(); // template download
        UploadEntity.ResourceType resourceType = uploadEntity.getResourceType();

        String fileSavedTempLocation = uploadEntity.getInstallPathPrefix() + "/" + filename;
        String dummyFileName = "dummy." + uploadEntity.getFormat().getFileExtension();
        if (ImageStoreUtil.isCompressedExtension(filename)) {
            String uploadedFileExtension = FilenameUtils.getExtension(filename);
            dummyFileName += "." + uploadedFileExtension;
        }

        String formatError = ImageStoreUtil.checkTemplateFormat(fileSavedTempLocation, dummyFileName);
        if (StringUtils.isNotBlank(formatError)) {
            String errorString = "File type mismatch between uploaded file and selected format. Selected file format: " + uploadEntity.getFormat() + ". Received: " + formatError;
            logger.error(errorString);
            return errorString;
        }

        int imgSizeGigs = getSizeInGB(_storage.getSize(fileSavedTempLocation));
        int maxSize = uploadEntity.getMaxSizeInGB();
        if (imgSizeGigs > maxSize) {
            String errorMessage = "Maximum file upload size exceeded. Physical file size: " + imgSizeGigs + "GB. Maximum allowed size: " + maxSize + "GB.";
            logger.error(errorMessage);
            return errorMessage;
        }
        imgSizeGigs++; // add one just in case
        long timeout = (long)imgSizeGigs * installTimeoutPerGig;
        Script scr = new Script(getScriptLocation(resourceType), timeout, logger);
        scr.add("-s", Integer.toString(imgSizeGigs));
        scr.add("-S", Long.toString(UploadEntity.s_maxTemplateSize));
        if (uploadEntity.getDescription() != null && uploadEntity.getDescription().length() > 1) {
            scr.add("-d", uploadEntity.getDescription());
        }
        if (uploadEntity.isHvm()) {
            scr.add("-h");
        }
        String checkSum = uploadEntity.getChksum();
        if (StringUtils.isNotBlank(checkSum)) {
            scr.add("-c", checkSum);
        }

        // add options common to ISO and template
        String extension = uploadEntity.getFormat().getFileExtension();
        String templateName = "";
        if (extension.equals("iso")) {
            templateName = uploadEntity.getUuid().trim().replace(" ", "_");
        } else {
            try {
                templateName = UUID.nameUUIDFromBytes((uploadEntity.getFilename() + System.currentTimeMillis()).getBytes("UTF-8")).toString();
            } catch (UnsupportedEncodingException e) {
                templateName = uploadEntity.getUuid().trim().replace(" ", "_");
            }
        }

        // run script to mv the temporary template file to the final template
        // file
        String templateFilename = templateName + "." + extension;
        uploadEntity.setTemplatePath(finalResourcePath + "/" + templateFilename);
        scr.add("-n", templateFilename);

        scr.add("-t", resourcePath);
        scr.add("-f", fileSavedTempLocation); // this is the temporary
        // template file downloaded
        if (uploadEntity.getChksum() != null && uploadEntity.getChksum().length() > 1) {
            scr.add("-c", uploadEntity.getChksum());
        }
        scr.add("-u"); // cleanup
        String result;
        result = scr.execute();

        if (result != null) {
            return result;
        }

        String finalFilename = resourcePath + "/" + templateFilename;

        if (ImageStoreUtil.isCorrectExtension(finalFilename, "qcow2")) {
            try {
                Qcow2Inspector.validateQcow2File(finalFilename);
            } catch (RuntimeException e) {
                logger.error(String.format("Uploaded file [%s] is not a valid QCOW2.", finalFilename), e);
                return "The uploaded file is not a valid QCOW2. Ask the administrator to check the logs for more details.";
            }
        }

        // Set permissions for the downloaded template
        File downloadedTemplate = new File(finalFilename);
        _storage.setWorldReadableAndWriteable(downloadedTemplate);

        // Set permissions for template/volume.properties
        String propertiesFile = resourcePath;
        if (resourceType == UploadEntity.ResourceType.TEMPLATE) {
            propertiesFile += "/template.properties";
        } else {
            propertiesFile += "/volume.properties";
        }
        File templateProperties = new File(propertiesFile);
        _storage.setWorldReadableAndWriteable(templateProperties);

        TemplateLocation loc = new TemplateLocation(_storage, resourcePath);
        try {
            loc.create(uploadEntity.getEntityId(), true, uploadEntity.getFilename());
        } catch (IOException e) {
            logger.warn("Something is wrong with template location " + resourcePath, e);
            loc.purge();
            return "Unable to upload due to " + e.getMessage();
        }

        Map<String, Processor> processors = _dlMgr.getProcessors();
        for (Processor processor : processors.values()) {
            FormatInfo info = null;
            try {
                info = processor.process(resourcePath, null, templateName, processTimeout * 1000);
            } catch (InternalErrorException e) {
                logger.error("Template process exception ", e);
                return e.toString();
            }
            if (info != null) {
                loc.addFormat(info);
                uploadEntity.setVirtualSize(info.virtualSize);
                uploadEntity.setPhysicalSize(info.size);
                if (info.ovfInformationTO != null) {
                    uploadEntity.setOvfInformationTO(info.ovfInformationTO);
                }
                break;
            }
        }

        if (!loc.save()) {
            logger.warn("Cleaning up because we're unable to save the formats");
            loc.purge();
        }
        uploadEntity.setStatus(UploadEntity.Status.COMPLETED);
        uploadEntityStateMap.put(uploadEntity.getUuid(), uploadEntity);
        return null;
    }

    protected String getPostUploadPSK() {
        if (_ssvmPSK == null) {
            try {
                _ssvmPSK = FileUtils.readFileToString(new File(POST_UPLOAD_KEY_LOCATION), "utf-8");
            } catch (IOException e) {
                logger.debug("Error while reading SSVM PSK from location " + POST_UPLOAD_KEY_LOCATION, e);
            }
        }
        return _ssvmPSK;
    }

    public void updateStateMapWithError(String uuid, String errorMessage) {
        UploadEntity uploadEntity = null;
        if (uploadEntityStateMap.get(uuid) != null) {
            uploadEntity = uploadEntityStateMap.get(uuid);
        } else {
            uploadEntity = new UploadEntity();
        }
        uploadEntity.setStatus(UploadEntity.Status.ERROR);
        uploadEntity.setErrorMessage(errorMessage);
        uploadEntityStateMap.put(uuid, uploadEntity);
    }

    public void validatePostUploadRequest(String signature, String metadata, String timeout, String hostname, long contentLength, String uuid)
            throws InvalidParameterValueException {
        // check none of the params are empty
        if (StringUtils.isAnyEmpty(signature, metadata, timeout)) {
            updateStateMapWithError(uuid, "signature, metadata and expires are compulsory fields.");
            throw new InvalidParameterValueException("signature, metadata and expires are compulsory fields.");
        }

        //check that contentLength exists and is greater than zero
        if (contentLength <= 0) {
            throw new InvalidParameterValueException("content length is not set in the request or has invalid value.");
        }

        validatePostUploadRequestSignature(signature, hostname, uuid, metadata, timeout);

        //validate timeout
        DateTime timeoutDateTime = DateTime.parse(timeout, ISODateTimeFormat.dateTime());
        if (timeoutDateTime.isBeforeNow()) {
            updateStateMapWithError(uuid, "request not valid anymore.");
            throw new InvalidParameterValueException("request not valid anymore.");
        }
    }

    /**
     * Validates whether the provided signature matches the signature generated from the other parameters;
     * throws an InvalidParameterValueException if it does not.
     */
    protected void validatePostUploadRequestSignature(String signature, String hostname, String uuid, String metadata, String timeout) {
        logger.trace(String.format("Validating signature [%s] for post upload request [%s].", signature, uuid));
        String protocol = getUploadProtocol();
        String fullUrl = String.format("%s://%s/upload/%s", protocol, hostname, uuid);
        String data = String.format("%s%s%s", metadata, fullUrl, timeout);

        String computedSignature = EncryptionUtil.generateSignature(data, getPostUploadPSK());
        logger.debug(String.format("Computed signature for post upload request [%s] is [%s].", uuid, computedSignature));

        boolean isSignatureValid = computedSignature.equals(signature);
        if (!isSignatureValid) {
            logger.debug(String.format("Signature for post upload request [%s] is invalid.", uuid));
            String errorMsg = "signature validation failed.";
            updateStateMapWithError(uuid, errorMsg);
            throw new InvalidParameterValueException(errorMsg);
        }
        logger.debug(String.format("Signature for post upload request [%s] is valid.", uuid));
    }

    /**
     * Returns the protocol used for uploads as a string.
     */
    protected String getUploadProtocol() {
        if (useHttpsToUpload()) {
            logger.debug(String.format("Param [%s] is set to true; therefore, HTTPS is being used.", USE_HTTPS_TO_UPLOAD));
            return NetUtils.HTTPS_PROTO;
        }
        logger.debug(String.format("Param [%s] is set to false; therefore, HTTP is being used.", USE_HTTPS_TO_UPLOAD));
        return NetUtils.HTTP_PROTO;
    }

    /**
     * Retrieves the value of "useHttpsToUpload" from the params as a boolean
     */
    protected boolean useHttpsToUpload() {
        return BooleanUtils.toBoolean((String) _params.get(USE_HTTPS_TO_UPLOAD));
    }

    private TemplateOrVolumePostUploadCommand getTemplateOrVolumePostUploadCmd(String metadata) {
        TemplateOrVolumePostUploadCommand cmd = null;
        try {
            Gson gson = new GsonBuilder().create();
            cmd = gson.fromJson(EncryptionUtil.decodeData(metadata, getPostUploadPSK()), TemplateOrVolumePostUploadCommand.class);
        } catch (Exception ex) {
            logger.error("exception while decoding and deserialising metadata", ex);
        }
        return cmd;
    }

    protected Answer execute(QuerySnapshotZoneCopyCommand cmd) {
        SnapshotObjectTO snapshot = cmd.getSnapshot();
        String parentPath = getRootDir(snapshot.getDataStore().getUrl(), _nfsVersion);
        String path = snapshot.getPath();
        File snapFile = new File(parentPath + File.separator + path);
        if (snapFile.exists() && !snapFile.isDirectory()) {
            return new QuerySnapshotZoneCopyAnswer(cmd, List.of(path));
        }
        int index = path.lastIndexOf(File.separator);
        String snapDir = path.substring(0, index);
        List<String> files = new ArrayList<>();
        try (Stream<Path> stream = Files.list(Paths.get(parentPath + File.separator + snapDir))) {
            List<String> fileNames = stream
                    .filter(file -> !Files.isDirectory(file))
                    .map(Path::getFileName)
                    .map(Path::toString)
                    .collect(Collectors.toList());
            for (String file : fileNames) {
                file = snapDir + "/" + file;
                logger.debug(String.format("Found snapshot file %s", file));
                files.add(file);
            }
        } catch (IOException ioe) {
            logger.error("Error preparing file list for snapshot copy", ioe);
        }
        return new QuerySnapshotZoneCopyAnswer(cmd, files);
    }

}
