/*
 * 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 com.cloud.hypervisor.xenserver.resource;

import static com.cloud.utils.ReflectUtil.flattenProperties;
import static com.google.common.collect.Lists.newArrayList;

import java.io.File;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.cloudstack.storage.command.AttachAnswer;
import org.apache.cloudstack.storage.command.AttachCommand;
import org.apache.cloudstack.storage.command.AttachPrimaryDataStoreAnswer;
import org.apache.cloudstack.storage.command.AttachPrimaryDataStoreCmd;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.command.CreateObjectCommand;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.DettachAnswer;
import org.apache.cloudstack.storage.command.DettachCommand;
import org.apache.cloudstack.storage.command.ForgetObjectCmd;
import org.apache.cloudstack.storage.command.IntroduceObjectAnswer;
import org.apache.cloudstack.storage.command.IntroduceObjectCmd;
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
import org.apache.cloudstack.storage.datastore.protocol.DataStoreProtocol;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import org.apache.xmlrpc.XmlRpcException;

import com.cloud.agent.api.Answer;
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.DiskTO;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.S3TO;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.agent.api.to.SwiftTO;
import com.cloud.exception.InternalErrorException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase.SRType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.resource.StorageProcessor;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.storage.encoding.DecodedDataObject;
import com.cloud.utils.storage.encoding.DecodedDataStore;
import com.cloud.utils.storage.encoding.Decoder;
import com.cloud.utils.storage.S3.ClientOptions;
import com.xensource.xenapi.Connection;
import com.xensource.xenapi.Host;
import com.xensource.xenapi.PBD;
import com.xensource.xenapi.SR;
import com.xensource.xenapi.Types;
import com.xensource.xenapi.Types.BadServerResponse;
import com.xensource.xenapi.Types.VmPowerState;
import com.xensource.xenapi.Types.XenAPIException;
import com.xensource.xenapi.VBD;
import com.xensource.xenapi.VDI;
import com.xensource.xenapi.VM;

public class XenServerStorageProcessor implements StorageProcessor {
    private static final Logger s_logger = Logger.getLogger(XenServerStorageProcessor.class);
    protected CitrixResourceBase hypervisorResource;
    protected String BaseMountPointOnHost = "/var/run/cloud_mount";

    public XenServerStorageProcessor(final CitrixResourceBase resource) {
        hypervisorResource = resource;
    }

    // if the source SR needs to be attached to, do so
    // take a snapshot of the source VDI (on the source SR)
    // create an iSCSI SR based on the new back-end volume
    // copy the snapshot to the new SR
    // delete the snapshot
    // detach the new SR
    // if we needed to perform an attach to the source SR, detach from it
    @Override
    public SnapshotAndCopyAnswer snapshotAndCopy(final SnapshotAndCopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();

        try {
            SR sourceSr = null;

            final Map<String, String> sourceDetails = cmd.getSourceDetails();

            if (sourceDetails != null && sourceDetails.keySet().size() > 0) {
                final String iScsiName = sourceDetails.get(DiskTO.IQN);
                final String storageHost = sourceDetails.get(DiskTO.STORAGE_HOST);
                final String chapInitiatorUsername = sourceDetails.get(DiskTO.CHAP_INITIATOR_USERNAME);
                final String chapInitiatorSecret = sourceDetails.get(DiskTO.CHAP_INITIATOR_SECRET);

                sourceSr = hypervisorResource.getIscsiSR(conn, iScsiName, storageHost, iScsiName, chapInitiatorUsername, chapInitiatorSecret, false);
            }

            final VDI vdiToSnapshot = VDI.getByUuid(conn, cmd.getUuidOfSourceVdi());

            final VDI vdiSnapshot = vdiToSnapshot.snapshot(conn, new HashMap<String, String>());

            final Map<String, String> destDetails = cmd.getDestDetails();

            final String iScsiName = destDetails.get(DiskTO.IQN);
            final String storageHost = destDetails.get(DiskTO.STORAGE_HOST);
            final String chapInitiatorUsername = destDetails.get(DiskTO.CHAP_INITIATOR_USERNAME);
            final String chapInitiatorSecret = destDetails.get(DiskTO.CHAP_INITIATOR_SECRET);

            final SR newSr = hypervisorResource.getIscsiSR(conn, iScsiName, storageHost, iScsiName, chapInitiatorUsername, chapInitiatorSecret, false);

            final VDI vdiCopy = vdiSnapshot.copy(conn, newSr);

            final String vdiUuid = vdiCopy.getUuid(conn);

            vdiSnapshot.destroy(conn);

            if (sourceSr != null) {
                hypervisorResource.removeSR(conn, sourceSr);
            }

            hypervisorResource.removeSR(conn, newSr);

            final SnapshotAndCopyAnswer snapshotAndCopyAnswer = new SnapshotAndCopyAnswer();

            snapshotAndCopyAnswer.setPath(vdiUuid);

            return snapshotAndCopyAnswer;
        }
        catch (final Exception ex) {
            s_logger.warn("Failed to take and copy snapshot: " + ex.toString(), ex);

            return new SnapshotAndCopyAnswer(ex.getMessage());
        }
    }

    @Override
    public AttachAnswer attachIso(final AttachCommand cmd) {
        final DiskTO disk = cmd.getDisk();
        final DataTO data = disk.getData();
        final DataStoreTO store = data.getDataStore();

        String isoURL = null;
        if (store == null) {
            final TemplateObjectTO iso = (TemplateObjectTO) disk.getData();
            isoURL = iso.getName();
        } else {
            if (!(store instanceof NfsTO)) {
                s_logger.debug("Can't attach a iso which is not created on nfs: ");
                return new AttachAnswer("Can't attach a iso which is not created on nfs: ");
            }
            final NfsTO nfsStore = (NfsTO) store;
            isoURL = nfsStore.getUrl() + nfsStore.getPathSeparator() + data.getPath();
        }

        final String vmName = cmd.getVmName();
        try {
            final Connection conn = hypervisorResource.getConnection();

            VBD isoVBD = null;

            // Find the VM
            final VM vm = hypervisorResource.getVM(conn, vmName);
            // Find the ISO VDI
            final VDI isoVDI = hypervisorResource.getIsoVDIByURL(conn, vmName, isoURL);

            // Find the VM's CD-ROM VBD
            final Set<VBD> vbds = vm.getVBDs(conn);
            for (final VBD vbd : vbds) {
                final String userDevice = vbd.getUserdevice(conn);
                final Types.VbdType type = vbd.getType(conn);

                if (userDevice.equals("3") && type == Types.VbdType.CD) {
                    isoVBD = vbd;
                    break;
                }
            }

            if (isoVBD == null) {
                throw new CloudRuntimeException("Unable to find CD-ROM VBD for VM: " + vmName);
            } else {
                // If an ISO is already inserted, eject it
                if (!isoVBD.getEmpty(conn)) {
                    isoVBD.eject(conn);
                }

                // Insert the new ISO
                isoVBD.insert(conn, isoVDI);
            }

            return new AttachAnswer(disk);

        } catch (final XenAPIException e) {
            s_logger.warn("Failed to attach iso" + ": " + e.toString(), e);
            return new AttachAnswer(e.toString());
        } catch (final Exception e) {
            s_logger.warn("Failed to attach iso" + ": " + e.toString(), e);
            return new AttachAnswer(e.toString());
        }
    }

    @Override
    public AttachAnswer attachVolume(final AttachCommand cmd) {
        final DiskTO disk = cmd.getDisk();
        final DataTO data = disk.getData();
        try {
            final String vmName = cmd.getVmName();
            final String vdiNameLabel = vmName + "-DATA";

            final Connection conn = hypervisorResource.getConnection();
            VM vm = null;

            boolean vmNotRunning = true;

            try {
                vm = hypervisorResource.getVM(conn, vmName);

                final VM.Record vmr = vm.getRecord(conn);

                vmNotRunning = vmr.powerState != VmPowerState.RUNNING;
            } catch (final CloudRuntimeException ex) {
            }

            final Map<String, String> details = disk.getDetails();
            final boolean isManaged = Boolean.parseBoolean(details.get(DiskTO.MANAGED));

            // if the VM is not running and we're not dealing with managed storage, just return success (nothing to do here)
            // this should probably never actually happen
            if (vmNotRunning && !isManaged) {
                return new AttachAnswer(disk);
            }

            VDI vdi = null;

            if (isManaged) {
                vdi = hypervisorResource.prepareManagedStorage(conn, details, data.getPath(), vdiNameLabel);

                if (vmNotRunning) {
                    final DiskTO newDisk = new DiskTO(disk.getData(), disk.getDiskSeq(), vdi.getUuid(conn), disk.getType());

                    return new AttachAnswer(newDisk);
                }
            } else {
                vdi = hypervisorResource.mount(conn, null, null, data.getPath());
            }

            hypervisorResource.destroyUnattachedVBD(conn, vm);

            final VBD.Record vbdr = new VBD.Record();

            vbdr.VM = vm;
            vbdr.VDI = vdi;
            vbdr.bootable = false;
            vbdr.userdevice = "autodetect";
            final Long deviceId = disk.getDiskSeq();
            if (deviceId != null && !hypervisorResource.isDeviceUsed(conn, vm, deviceId)) {
                vbdr.userdevice = deviceId.toString();
            }
            vbdr.mode = Types.VbdMode.RW;
            vbdr.type = Types.VbdType.DISK;
            vbdr.unpluggable = true;

            final VBD vbd = VBD.create(conn, vbdr);

            // Attach the VBD to the VM
            try {
                vbd.plug(conn);
            } catch (final Exception e) {
                vbd.destroy(conn);
                throw e;
            }
            // Update the VDI's label to include the VM name
            vdi.setNameLabel(conn, vdiNameLabel);

            final DiskTO newDisk = new DiskTO(disk.getData(), Long.parseLong(vbd.getUserdevice(conn)), vdi.getUuid(conn), disk.getType());

            return new AttachAnswer(newDisk);
        } catch (final Exception e) {
            final String msg = "Failed to attach volume for uuid: " + data.getPath() + " due to "  + e.toString();
            s_logger.warn(msg, e);
            return new AttachAnswer(msg);
        }
    }

    @Override
    public Answer dettachIso(final DettachCommand cmd) {
        final DiskTO disk = cmd.getDisk();
        final DataTO data = disk.getData();
        final DataStoreTO store = data.getDataStore();

        String isoURL = null;
        if (store == null) {
            final TemplateObjectTO iso = (TemplateObjectTO) disk.getData();
            isoURL = iso.getName();
        } else {
            if (!(store instanceof NfsTO)) {
                s_logger.debug("Can't attach a iso which is not created on nfs: ");
                return new AttachAnswer("Can't attach a iso which is not created on nfs: ");
            }
            final NfsTO nfsStore = (NfsTO) store;
            isoURL = nfsStore.getUrl() + nfsStore.getPathSeparator() + data.getPath();
        }

        try {
            final Connection conn = hypervisorResource.getConnection();
            // Find the VM
            final VM vm = hypervisorResource.getVM(conn, cmd.getVmName());
            final String vmUUID = vm.getUuid(conn);

            // Find the ISO VDI
            final VDI isoVDI = hypervisorResource.getIsoVDIByURL(conn, cmd.getVmName(), isoURL);

            final SR sr = isoVDI.getSR(conn);

            // Look up all VBDs for this VDI
            final Set<VBD> vbds = isoVDI.getVBDs(conn);

            // Iterate through VBDs, and if the VBD belongs the VM, eject
            // the ISO from it
            for (final VBD vbd : vbds) {
                final VM vbdVM = vbd.getVM(conn);
                final String vbdVmUUID = vbdVM.getUuid(conn);

                if (vbdVmUUID.equals(vmUUID)) {
                    // If an ISO is already inserted, eject it
                    if (!vbd.getEmpty(conn)) {
                        vbd.eject(conn);
                    }
                    break;
                }
            }

            if (!sr.getNameLabel(conn).startsWith("XenServer Tools")) {
                hypervisorResource.removeSR(conn, sr);
            }

            return new DettachAnswer(disk);
        } catch (final XenAPIException e) {
            final String msg = "Failed to dettach volume" + " for uuid: " + data.getPath() + "  due to " + e.toString();
            s_logger.warn(msg, e);
            return new DettachAnswer(msg);
        } catch (final Exception e) {
            final String msg = "Failed to dettach volume" + " for uuid: " + data.getPath() + "  due to " + e.getMessage();
            s_logger.warn(msg, e);
            return new DettachAnswer(msg);
        }
    }

    @Override
    public Answer dettachVolume(final DettachCommand cmd) {
        final DiskTO disk = cmd.getDisk();
        final DataTO data = disk.getData();

        try {
            final Connection conn = hypervisorResource.getConnection();

            final String vmName = cmd.getVmName();
            VM vm = null;

            boolean vmNotRunning = true;

            try {
                vm = hypervisorResource.getVM(conn, vmName);

                final VM.Record vmr = vm.getRecord(conn);

                vmNotRunning = vmr.powerState != VmPowerState.RUNNING;
            } catch (final CloudRuntimeException ex) {
            }

            // if the VM is not running and we're not dealing with managed storage, just return success (nothing to do here)
            // this should probably never actually happen
            if (vmNotRunning && !cmd.isManaged()) {
                return new DettachAnswer(disk);
            }

            if (!vmNotRunning) {
                final VDI vdi = hypervisorResource.mount(conn, null, null, data.getPath());

                // Look up all VBDs for this VDI
                final Set<VBD> vbds = vdi.getVBDs(conn);

                // Detach each VBD from its VM, and then destroy it
                for (final VBD vbd : vbds) {
                    final VBD.Record vbdr = vbd.getRecord(conn);

                    if (vbdr.currentlyAttached) {
                        vbd.unplug(conn);
                    }

                    vbd.destroy(conn);
                }

                // Update the VDI's label to be "detached"
                vdi.setNameLabel(conn, "detached");

                hypervisorResource.umount(conn, vdi);
            }

            if (cmd.isManaged()) {
                hypervisorResource.handleSrAndVdiDetach(cmd.get_iScsiName(), conn);
            }

            return new DettachAnswer(disk);
        } catch (final Exception e) {
            s_logger.warn("Failed dettach volume: " + data.getPath());
            return new DettachAnswer("Failed dettach volume: " + data.getPath() + ", due to " + e.toString());
        }
    }

    protected SR getSRByNameLabel(final Connection conn, final String nameLabel) throws BadServerResponse, XenAPIException, XmlRpcException {
        final Set<SR> srs = SR.getByNameLabel(conn, nameLabel);
        if (srs.size() != 1) {
            throw new CloudRuntimeException("storage uuid: " + nameLabel + " is not unique");
        }
        final SR poolsr = srs.iterator().next();
        return poolsr;
    }

    protected VDI createVdi(final Connection conn, final String vdiName, final SR sr, final long size) throws BadServerResponse, XenAPIException, XmlRpcException {
        final VDI.Record vdir = new VDI.Record();
        vdir.nameLabel = vdiName;
        vdir.SR = sr;
        vdir.type = Types.VdiType.USER;

        vdir.virtualSize = size;
        final VDI vdi = VDI.create(conn, vdir);
        return vdi;
    }

    protected void deleteVDI(final Connection conn, final VDI vdi) throws BadServerResponse, XenAPIException, XmlRpcException {
        vdi.destroy(conn);
    }

    @Override
    public Answer createSnapshot(final CreateObjectCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final SnapshotObjectTO snapshotTO = (SnapshotObjectTO) cmd.getData();
        final long snapshotId = snapshotTO.getId();
        final String snapshotName = snapshotTO.getName();
        String details = "create snapshot operation Failed for snapshotId: " + snapshotId;
        String snapshotUUID = null;

        try {
            final String volumeUUID = snapshotTO.getVolume().getPath();
            final VDI volume = VDI.getByUuid(conn, volumeUUID);

            final VDI snapshot = volume.snapshot(conn, new HashMap<String, String>());

            if (snapshotName != null) {
                snapshot.setNameLabel(conn, snapshotName);
            }

            snapshotUUID = snapshot.getUuid(conn);
            final String preSnapshotUUID = snapshotTO.getParentSnapshotPath();
            //check if it is a empty snapshot
            if (preSnapshotUUID != null) {
                final SR sr = volume.getSR(conn);
                final String srUUID = sr.getUuid(conn);
                final String type = sr.getType(conn);
                final Boolean isISCSI = IsISCSI(type);
                final String snapshotParentUUID = getVhdParent(conn, srUUID, snapshotUUID, isISCSI);

                try {
                    final String preSnapshotParentUUID = getVhdParent(conn, srUUID, preSnapshotUUID, isISCSI);
                    if (snapshotParentUUID != null && snapshotParentUUID.equals(preSnapshotParentUUID)) {
                        // this is empty snapshot, remove it
                        snapshot.destroy(conn);
                        snapshotUUID = preSnapshotUUID;
                    }
                } catch (final Exception e) {
                    s_logger.debug("Failed to get parent snapshot", e);
                }
            }
            final SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
            newSnapshot.setPath(snapshotUUID);
            return new CreateObjectAnswer(newSnapshot);
        } catch (final XenAPIException e) {
            details += ", reason: " + e.toString();
            s_logger.warn(details, e);
        } catch (final Exception e) {
            details += ", reason: " + e.toString();
            s_logger.warn(details, e);
        }

        return new CreateObjectAnswer(details);
    }

    @Override
    public Answer deleteVolume(final DeleteCommand cmd) {
        final DataTO volume = cmd.getData();
        final Connection conn = hypervisorResource.getConnection();
        String errorMsg = null;
        try {
            final VDI vdi = VDI.getByUuid(conn, volume.getPath());
            deleteVDI(conn, vdi);
            return new Answer(null);
        } catch (final BadServerResponse e) {
            s_logger.debug("Failed to delete volume", e);
            errorMsg = e.toString();
        } catch (final XenAPIException e) {
            s_logger.debug("Failed to delete volume", e);
            errorMsg = e.toString();
        } catch (final XmlRpcException e) {
            s_logger.debug("Failed to delete volume", e);
            errorMsg = e.toString();
        }
        return new Answer(null, false, errorMsg);
    }

    protected SR getNfsSR(final Connection conn, final StorageFilerTO pool) {
        final Map<String, String> deviceConfig = new HashMap<String, String>();
        try {
            final String server = pool.getHost();
            String serverpath = pool.getPath();
            serverpath = serverpath.replace("//", "/");
            final Set<SR> srs = SR.getAll(conn);
            for (final SR sr : srs) {
                if (!SRType.NFS.equals(sr.getType(conn))) {
                    continue;
                }

                final Set<PBD> pbds = sr.getPBDs(conn);
                if (pbds.isEmpty()) {
                    continue;
                }

                final PBD pbd = pbds.iterator().next();

                final Map<String, String> dc = pbd.getDeviceConfig(conn);

                if (dc == null) {
                    continue;
                }

                if (dc.get("server") == null) {
                    continue;
                }

                if (dc.get("serverpath") == null) {
                    continue;
                }

                if (server.equals(dc.get("server")) && serverpath.equals(dc.get("serverpath"))) {
                    throw new CloudRuntimeException("There is a SR using the same configuration server:" + dc.get("server") + ", serverpath:" + dc.get("serverpath") +
                            " for pool " + pool.getUuid() + "on host:" + hypervisorResource.getHost().getUuid());
                }

            }
            deviceConfig.put("server", server);
            deviceConfig.put("serverpath", serverpath);
            final Host host = Host.getByUuid(conn, hypervisorResource.getHost().getUuid());
            final Map<String, String> smConfig = new HashMap<String, String>();
            smConfig.put("nosubdir", "true");
            final SR sr = SR.create(conn, host, deviceConfig, new Long(0), pool.getUuid(), Long.toString(pool.getId()), SRType.NFS.toString(), "user", true, smConfig);
            sr.scan(conn);
            return sr;
        } catch (final XenAPIException e) {
            throw new CloudRuntimeException("Unable to create NFS SR " + pool.toString(), e);
        } catch (final XmlRpcException e) {
            throw new CloudRuntimeException("Unable to create NFS SR " + pool.toString(), e);
        }
    }

    protected Answer directDownloadHttpTemplate(final CopyCommand cmd, final DecodedDataObject srcObj, final DecodedDataObject destObj) {
        final Connection conn = hypervisorResource.getConnection();
        SR poolsr = null;
        VDI vdi = null;
        boolean result = false;
        try {
            if (destObj.getPath() == null) {
                //need to create volume at first

            }
            vdi = VDI.getByUuid(conn, destObj.getPath());
            if (vdi == null) {
                throw new CloudRuntimeException("can't find volume: " + destObj.getPath());
            }
            final String destStoreUuid = destObj.getStore().getUuid();
            final Set<SR> srs = SR.getByNameLabel(conn, destStoreUuid);
            if (srs.size() != 1) {
                throw new CloudRuntimeException("storage uuid: " + destStoreUuid + " is not unique");
            }
            poolsr = srs.iterator().next();
            final VDI.Record vdir = vdi.getRecord(conn);
            final String vdiLocation = vdir.location;
            String pbdLocation = null;
            if (destObj.getStore().getScheme().equalsIgnoreCase(DataStoreProtocol.NFS.toString())) {
                pbdLocation = "/run/sr-mount/" + poolsr.getUuid(conn);
            } else {
                final Set<PBD> pbds = poolsr.getPBDs(conn);
                if (pbds.size() != 1) {
                    throw new CloudRuntimeException("Don't how to handle multiple pbds:" + pbds.size() + " for sr: " + poolsr.getUuid(conn));
                }
                final PBD pbd = pbds.iterator().next();
                final Map<String, String> deviceCfg = pbd.getDeviceConfig(conn);
                pbdLocation = deviceCfg.get("location");
            }
            if (pbdLocation == null) {
                throw new CloudRuntimeException("Can't get pbd location");
            }

            final String vdiPath = pbdLocation + "/" + vdiLocation + ".vhd";
            //download a url into vdipath
            //downloadHttpToLocalFile(vdiPath, template.getPath());
            hypervisorResource.callHostPlugin(conn, "storagePlugin", "downloadTemplateFromUrl", "destPath", vdiPath, "srcUrl", srcObj.getPath());
            result = true;
            //return new CopyCmdAnswer(cmd, vdi.getUuid(conn));
        } catch (final BadServerResponse e) {
            s_logger.debug("Failed to download template", e);
        } catch (final XenAPIException e) {
            s_logger.debug("Failed to download template", e);
        } catch (final XmlRpcException e) {
            s_logger.debug("Failed to download template", e);
        } catch (final Exception e) {
            s_logger.debug("Failed to download template", e);
        } finally {
            if (!result && vdi != null) {
                try {
                    vdi.destroy(conn);
                } catch (final BadServerResponse e) {
                    s_logger.debug("Failed to cleanup newly created vdi");
                } catch (final XenAPIException e) {
                    s_logger.debug("Failed to cleanup newly created vdi");
                } catch (final XmlRpcException e) {
                    s_logger.debug("Failed to cleanup newly created vdi");
                }
            }
        }
        return new Answer(cmd, false, "Failed to download template");
    }

    protected Answer execute(final AttachPrimaryDataStoreCmd cmd) {
        final String dataStoreUri = cmd.getDataStore();
        final Connection conn = hypervisorResource.getConnection();
        try {
            final DecodedDataObject obj = Decoder.decode(dataStoreUri);

            final DecodedDataStore store = obj.getStore();

            final SR sr = hypervisorResource.getStorageRepository(conn, store.getUuid());
            hypervisorResource.setupHeartbeatSr(conn, sr, false);
            final long capacity = sr.getPhysicalSize(conn);
            final long available = capacity - sr.getPhysicalUtilisation(conn);
            if (capacity == -1) {
                final String msg = "Pool capacity is -1! pool: ";
                s_logger.warn(msg);
                return new Answer(cmd, false, msg);
            }
            final AttachPrimaryDataStoreAnswer answer = new AttachPrimaryDataStoreAnswer(cmd);
            answer.setCapacity(capacity);
            answer.setUuid(sr.getUuid(conn));
            answer.setAvailable(available);
            return answer;
        } catch (final XenAPIException e) {
            final String msg = "AttachPrimaryDataStoreCmd add XenAPIException:" + e.toString();
            s_logger.warn(msg, e);
            return new Answer(cmd, false, msg);
        } catch (final Exception e) {
            final String msg = "AttachPrimaryDataStoreCmd failed:" + e.getMessage();
            s_logger.warn(msg, e);
            return new Answer(cmd, false, msg);
        }
    }

    protected boolean IsISCSI(final String type) {
        return SRType.LVMOHBA.equals(type) || SRType.LVMOISCSI.equals(type) || SRType.LVM.equals(type);
    }

    private String copy_vhd_from_secondarystorage(final Connection conn, final String mountpoint, final String sruuid, final int wait) {
        final String nameLabel = "cloud-" + UUID.randomUUID().toString();
        final String results =
                hypervisorResource.callHostPluginAsync(conn, "vmopspremium", "copy_vhd_from_secondarystorage", wait, "mountpoint", mountpoint, "sruuid", sruuid, "namelabel",
                        nameLabel);
        String errMsg = null;
        if (results == null || results.isEmpty()) {
            errMsg = "copy_vhd_from_secondarystorage return null";
        } else {
            final String[] tmp = results.split("#");
            final String status = tmp[0];
            if (status.equals("0")) {
                return tmp[1];
            } else {
                errMsg = tmp[1];
            }
        }
        final String source = mountpoint.substring(mountpoint.lastIndexOf('/') + 1);
        if (hypervisorResource.killCopyProcess(conn, source)) {
            destroyVDIbyNameLabel(conn, nameLabel);
        }
        s_logger.warn(errMsg);
        throw new CloudRuntimeException(errMsg);
    }

    private void destroyVDIbyNameLabel(final Connection conn, final String nameLabel) {
        try {
            final Set<VDI> vdis = VDI.getByNameLabel(conn, nameLabel);
            if (vdis.size() != 1) {
                s_logger.warn("destoryVDIbyNameLabel failed due to there are " + vdis.size() + " VDIs with name " + nameLabel);
                return;
            }
            for (final VDI vdi : vdis) {
                try {
                    vdi.destroy(conn);
                } catch (final Exception e) {
                }
            }
        } catch (final Exception e) {
        }
    }

    protected VDI getVDIbyUuid(final Connection conn, final String uuid) {
        try {
            return VDI.getByUuid(conn, uuid);
        } catch (final Exception e) {
            final String msg = "Catch Exception " + e.getClass().getName() + " :VDI getByUuid for uuid: " + uuid + " failed due to " + e.toString();
            s_logger.debug(msg);
            throw new CloudRuntimeException(msg, e);
        }
    }

    protected String getVhdParent(final Connection conn, final String primaryStorageSRUuid, final String snapshotUuid, final Boolean isISCSI) {
        final String parentUuid =
                hypervisorResource.callHostPlugin(conn, "vmopsSnapshot", "getVhdParent", "primaryStorageSRUuid", primaryStorageSRUuid, "snapshotUuid", snapshotUuid,
                        "isISCSI", isISCSI.toString());

        if (parentUuid == null || parentUuid.isEmpty() || parentUuid.equalsIgnoreCase("None")) {
            s_logger.debug("Unable to get parent of VHD " + snapshotUuid + " in SR " + primaryStorageSRUuid);
            // errString is already logged.
            return null;
        }
        return parentUuid;
    }

    @Override
    public Answer copyTemplateToPrimaryStorage(final CopyCommand cmd) {
        final DataTO srcDataTo = cmd.getSrcTO();
        final DataTO destDataTo = cmd.getDestTO();
        final int wait = cmd.getWait();
        final DataStoreTO srcDataStoreTo = srcDataTo.getDataStore();

        try {
            if (srcDataStoreTo instanceof NfsTO && srcDataTo.getObjectType() == DataObjectType.TEMPLATE) {
                final NfsTO srcImageStore = (NfsTO) srcDataStoreTo;
                final TemplateObjectTO srcTemplateObjectTo = (TemplateObjectTO) srcDataTo;
                final String storeUrl = srcImageStore.getUrl();
                final URI uri = new URI(storeUrl);
                final String tmplPath = uri.getHost() + ":" + uri.getPath() + "/" + srcDataTo.getPath();
                final DataStoreTO destDataStoreTo = destDataTo.getDataStore();

                boolean managed = false;
                String storageHost = null;
                String managedStoragePoolName = null;
                String managedStoragePoolRootVolumeName = null;
                String managedStoragePoolRootVolumeSize = null;
                String chapInitiatorUsername = null;
                String chapInitiatorSecret = null;

                if (destDataStoreTo instanceof PrimaryDataStoreTO) {
                    final PrimaryDataStoreTO destPrimaryDataStoreTo = (PrimaryDataStoreTO)destDataStoreTo;

                    final Map<String, String> details = destPrimaryDataStoreTo.getDetails();

                    if (details != null) {
                        managed = Boolean.parseBoolean(details.get(PrimaryDataStoreTO.MANAGED));

                        if (managed) {
                            storageHost = details.get(PrimaryDataStoreTO.STORAGE_HOST);
                            managedStoragePoolName = details.get(PrimaryDataStoreTO.MANAGED_STORE_TARGET);
                            managedStoragePoolRootVolumeName = details.get(PrimaryDataStoreTO.MANAGED_STORE_TARGET_ROOT_VOLUME);
                            managedStoragePoolRootVolumeSize = details.get(PrimaryDataStoreTO.VOLUME_SIZE);
                            chapInitiatorUsername = details.get(PrimaryDataStoreTO.CHAP_INITIATOR_USERNAME);
                            chapInitiatorSecret = details.get(PrimaryDataStoreTO.CHAP_INITIATOR_SECRET);
                        }
                    }
                }

                final Connection conn = hypervisorResource.getConnection();

                final SR sr;

                if (managed) {
                    final Map<String, String> details = new HashMap<String, String>();

                    details.put(DiskTO.STORAGE_HOST, storageHost);
                    details.put(DiskTO.IQN, managedStoragePoolName);
                    details.put(DiskTO.VOLUME_SIZE, managedStoragePoolRootVolumeSize);
                    details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInitiatorUsername);
                    details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInitiatorSecret);

                    sr = hypervisorResource.prepareManagedSr(conn, details);
                } else {
                    final String srName = destDataStoreTo.getUuid();
                    final Set<SR> srs = SR.getByNameLabel(conn, srName);

                    if (srs.size() != 1) {
                        final String msg = "There are " + srs.size() + " SRs with same name: " + srName;

                        s_logger.warn(msg);

                        return new CopyCmdAnswer(msg);
                    } else {
                        sr = srs.iterator().next();
                    }
                }

                final String srUuid = sr.getUuid(conn);
                final String tmplUuid = copy_vhd_from_secondarystorage(conn, tmplPath, srUuid, wait);
                final VDI tmplVdi = getVDIbyUuid(conn, tmplUuid);

                final String uuidToReturn;
                final Long physicalSize = tmplVdi.getPhysicalUtilisation(conn);

                if (managed) {
                    uuidToReturn = tmplUuid;

                    tmplVdi.setNameLabel(conn, managedStoragePoolRootVolumeName);
                } else {
                    final VDI snapshotVdi = tmplVdi.snapshot(conn, new HashMap<String, String>());

                    uuidToReturn = snapshotVdi.getUuid(conn);

                    snapshotVdi.setNameLabel(conn, "Template " + srcTemplateObjectTo.getName());

                    tmplVdi.destroy(conn);
                }

                sr.scan(conn);

                try {
                    Thread.sleep(5000);
                } catch (final InterruptedException e) {
                }

                final TemplateObjectTO newVol = new TemplateObjectTO();

                newVol.setUuid(uuidToReturn);
                newVol.setPath(uuidToReturn);
                if (physicalSize != null) {
                    newVol.setSize(physicalSize);
                }
                newVol.setFormat(ImageFormat.VHD);

                return new CopyCmdAnswer(newVol);
            }
        } catch (final Exception e) {
            final String msg = "Catch Exception " + e.getClass().getName() + " for template + " + " due to " + e.toString();

            s_logger.warn(msg, e);

            return new CopyCmdAnswer(msg);
        }

        return new CopyCmdAnswer("not implemented yet");
    }

    @Override
    public Answer createVolume(final CreateObjectCommand cmd) {
        final DataTO data = cmd.getData();
        final VolumeObjectTO volume = (VolumeObjectTO) data;

        try {
            final Connection conn = hypervisorResource.getConnection();
            final SR poolSr = hypervisorResource.getStorageRepository(conn, data.getDataStore().getUuid());
            VDI.Record vdir = new VDI.Record();
            vdir.nameLabel = volume.getName();
            vdir.SR = poolSr;
            vdir.type = Types.VdiType.USER;

            vdir.virtualSize = volume.getSize();
            VDI vdi;

            vdi = VDI.create(conn, vdir);
            vdir = vdi.getRecord(conn);
            final VolumeObjectTO newVol = new VolumeObjectTO();
            newVol.setName(vdir.nameLabel);
            newVol.setSize(vdir.virtualSize);
            newVol.setPath(vdir.uuid);

            return new CreateObjectAnswer(newVol);
        } catch (final Exception e) {
            s_logger.debug("create volume failed: " + e.toString());
            return new CreateObjectAnswer(e.toString());
        }
    }

    @Override
    public Answer cloneVolumeFromBaseTemplate(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final DataTO srcData = cmd.getSrcTO();
        final DataTO destData = cmd.getDestTO();
        final VolumeObjectTO volume = (VolumeObjectTO) destData;
        VDI vdi = null;
        try {
            VDI tmpltvdi = null;

            tmpltvdi = getVDIbyUuid(conn, srcData.getPath());
            vdi = tmpltvdi.createClone(conn, new HashMap<String, String>());
            vdi.setNameLabel(conn, volume.getName());

            VDI.Record vdir;
            vdir = vdi.getRecord(conn);
            s_logger.debug("Succesfully created VDI: Uuid = " + vdir.uuid);

            final VolumeObjectTO newVol = new VolumeObjectTO();
            newVol.setName(vdir.nameLabel);
            newVol.setSize(vdir.virtualSize);
            newVol.setPath(vdir.uuid);

            return new CopyCmdAnswer(newVol);
        } catch (final Exception e) {
            s_logger.warn("Unable to create volume; Pool=" + destData + "; Disk: ", e);
            return new CopyCmdAnswer(e.toString());
        }
    }

    @Override
    public Answer copyVolumeFromImageCacheToPrimary(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final DataTO srcData = cmd.getSrcTO();
        final DataTO destData = cmd.getDestTO();
        final int wait = cmd.getWait();
        final VolumeObjectTO srcVolume = (VolumeObjectTO) srcData;
        final VolumeObjectTO destVolume = (VolumeObjectTO) destData;
        final DataStoreTO srcStore = srcVolume.getDataStore();

        if (srcStore instanceof NfsTO) {
            final NfsTO nfsStore = (NfsTO) srcStore;
            try {
                final SR primaryStoragePool = hypervisorResource.getStorageRepository(conn, destVolume.getDataStore().getUuid());
                final String srUuid = primaryStoragePool.getUuid(conn);
                final URI uri = new URI(nfsStore.getUrl());
                final String volumePath = uri.getHost() + ":" + uri.getPath() + nfsStore.getPathSeparator() + srcVolume.getPath();
                final String uuid = copy_vhd_from_secondarystorage(conn, volumePath, srUuid, wait);
                final VolumeObjectTO newVol = new VolumeObjectTO();
                newVol.setPath(uuid);
                newVol.setSize(srcVolume.getSize());

                return new CopyCmdAnswer(newVol);
            } catch (final Exception e) {
                final String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.toString();
                s_logger.warn(msg, e);
                return new CopyCmdAnswer(e.toString());
            }
        }

        s_logger.debug("unsupported protocol");
        return new CopyCmdAnswer("unsupported protocol");
    }

    @Override
    public Answer copyVolumeFromPrimaryToSecondary(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final VolumeObjectTO srcVolume = (VolumeObjectTO) cmd.getSrcTO();
        final VolumeObjectTO destVolume = (VolumeObjectTO) cmd.getDestTO();
        final int wait = cmd.getWait();
        final DataStoreTO destStore = destVolume.getDataStore();

        if (destStore instanceof NfsTO) {
            SR secondaryStorage = null;
            try {
                final NfsTO nfsStore = (NfsTO) destStore;
                final URI uri = new URI(nfsStore.getUrl());
                // Create the volume folder
                if (!hypervisorResource.createSecondaryStorageFolder(conn, uri.getHost() + ":" + uri.getPath(), destVolume.getPath())) {
                    throw new InternalErrorException("Failed to create the volume folder.");
                }

                // Create a SR for the volume UUID folder
                secondaryStorage = hypervisorResource.createNfsSRbyURI(conn, new URI(nfsStore.getUrl() + nfsStore.getPathSeparator() + destVolume.getPath()), false);
                // Look up the volume on the source primary storage pool
                final VDI srcVdi = getVDIbyUuid(conn, srcVolume.getPath());
                // Copy the volume to secondary storage
                final VDI destVdi = hypervisorResource.cloudVDIcopy(conn, srcVdi, secondaryStorage, wait);
                final String destVolumeUUID = destVdi.getUuid(conn);

                final VolumeObjectTO newVol = new VolumeObjectTO();
                newVol.setPath(destVolume.getPath() + nfsStore.getPathSeparator() + destVolumeUUID + ".vhd");
                newVol.setSize(srcVolume.getSize());
                return new CopyCmdAnswer(newVol);
            } catch (final Exception e) {
                s_logger.debug("Failed to copy volume to secondary: " + e.toString());
                return new CopyCmdAnswer("Failed to copy volume to secondary: " + e.toString());
            } finally {
                hypervisorResource.removeSR(conn, secondaryStorage);
            }
        }
        return new CopyCmdAnswer("unsupported protocol");
    }

    boolean swiftUpload(final Connection conn, final SwiftTO swift, final String container, final String ldir, final String lfilename, final Boolean isISCSI, final int wait) {
        String result = null;
        try {
            result =
                    hypervisorResource.callHostPluginAsync(conn, "swiftxenserver", "swift", wait, "op", "upload", "url", swift.getUrl(), "account", swift.getAccount(), "username",
                            swift.getUserName(), "key", swift.getKey(), "container", container, "ldir", ldir, "lfilename", lfilename, "isISCSI", isISCSI.toString());
            if (result != null && result.equals("true")) {
                return true;
            }
        } catch (final Exception e) {
            s_logger.warn("swift upload failed due to " + e.toString(), e);
        }
        return false;
    }

    protected String deleteSnapshotBackup(final Connection conn, final String localMountPoint, final String path, final String secondaryStorageMountPath, final String backupUUID) {

        // If anybody modifies the formatting below again, I'll skin them
        final String result =
                hypervisorResource.callHostPlugin(conn, "vmopsSnapshot", "deleteSnapshotBackup", "backupUUID", backupUUID, "path", path, "secondaryStorageMountPath",
                        secondaryStorageMountPath, "localMountPoint", localMountPoint);

        return result;
    }

    public String swiftBackupSnapshot(final Connection conn, final SwiftTO swift, final String srUuid, final String snapshotUuid, final String container, final Boolean isISCSI, final int wait) {
        String lfilename;
        String ldir;
        if (isISCSI) {
            ldir = "/dev/VG_XenStorage-" + srUuid;
            lfilename = "VHD-" + snapshotUuid;
        } else {
            ldir = "/var/run/sr-mount/" + srUuid;
            lfilename = snapshotUuid + ".vhd";
        }
        swiftUpload(conn, swift, container, ldir, lfilename, isISCSI, wait);
        return lfilename;
    }

    protected String backupSnapshotToS3(final Connection connection, final S3TO s3, final String srUuid, final String folder, final String snapshotUuid,
            final Boolean iSCSIFlag, final int wait) {

        final String filename = iSCSIFlag ? "VHD-" + snapshotUuid : snapshotUuid + ".vhd";
        final String dir = (iSCSIFlag ? "/dev/VG_XenStorage-" : "/var/run/sr-mount/") + srUuid;
        final String key = folder + "/" + filename; // String.format("/snapshots/%1$s", snapshotUuid);

        try {

            final List<String> parameters = newArrayList(flattenProperties(s3, ClientOptions.class));
            // https workaround for Introspector bug that does not
            // recognize Boolean accessor methods ...

            parameters.addAll(Arrays.asList("operation", "put", "filename", dir + "/" + filename, "iSCSIFlag", iSCSIFlag.toString(), "bucket", s3.getBucketName(), "key",
                    key, "https", s3.isHttps() != null ? s3.isHttps().toString() : "null", "maxSingleUploadSizeInBytes", String.valueOf(s3.getMaxSingleUploadSizeInBytes())));
            final String result = hypervisorResource.callHostPluginAsync(connection, "s3xenserver", "s3", wait, parameters.toArray(new String[parameters.size()]));

            if (result != null && result.equals("true")) {
                return key;
            }
            return null;

        } catch (final Exception e) {
            s_logger.error(String.format("S3 upload failed of snapshot %1$s due to %2$s.", snapshotUuid, e.toString()), e);
        }

        return null;

    }

    protected Long getSnapshotSize(final Connection conn, final String primaryStorageSRUuid, final String snapshotUuid, final Boolean isISCSI, final int wait) {
        final String physicalSize = hypervisorResource.callHostPluginAsync(conn, "vmopsSnapshot", "getSnapshotSize", wait,
                "primaryStorageSRUuid", primaryStorageSRUuid, "snapshotUuid", snapshotUuid, "isISCSI", isISCSI.toString());
        if (physicalSize == null || physicalSize.isEmpty()) {
            return (long) 0;
        } else {
            return Long.parseLong(physicalSize);
        }
    }

    protected String backupSnapshot(final Connection conn, final String primaryStorageSRUuid, final String localMountPoint, final String path, final String secondaryStorageMountPath,
            final String snapshotUuid, String prevBackupUuid, final Boolean isISCSI, final int wait) {
        String backupSnapshotUuid = null;

        if (prevBackupUuid == null) {
            prevBackupUuid = "";
        }

        // Each argument is put in a separate line for readability.
        // Using more lines does not harm the environment.
        final String backupUuid = UUID.randomUUID().toString();
        final String results =
                hypervisorResource.callHostPluginAsync(conn, "vmopsSnapshot", "backupSnapshot", wait, "primaryStorageSRUuid", primaryStorageSRUuid, "path", path,
                        "secondaryStorageMountPath", secondaryStorageMountPath, "snapshotUuid", snapshotUuid, "prevBackupUuid", prevBackupUuid, "backupUuid", backupUuid,
                        "isISCSI", isISCSI.toString(), "localMountPoint", localMountPoint);
        String errMsg = null;
        if (results == null || results.isEmpty()) {
            errMsg =
                    "Could not copy backupUuid: " + backupSnapshotUuid + " from primary storage " + primaryStorageSRUuid + " to secondary storage " +
                            secondaryStorageMountPath + " due to null";
        } else {

            final String[] tmp = results.split("#");
            final String status = tmp[0];
            backupSnapshotUuid = tmp[1];
            // status == "1" if and only if backupSnapshotUuid != null
            // So we don't rely on status value but return backupSnapshotUuid as an
            // indicator of success.
            if (status != null && status.equalsIgnoreCase("1") && backupSnapshotUuid != null) {
                s_logger.debug("Successfully copied backupUuid: " + backupSnapshotUuid + " to secondary storage");
                return results;
            } else {
                errMsg =
                        "Could not copy backupUuid: " + backupSnapshotUuid + " from primary storage " + primaryStorageSRUuid + " to secondary storage " +
                                secondaryStorageMountPath + " due to " + tmp[1];
            }
        }
        final String source = backupUuid + ".vhd";
        hypervisorResource.killCopyProcess(conn, source);
        s_logger.warn(errMsg);
        throw new CloudRuntimeException(errMsg);
    }

    protected boolean destroySnapshotOnPrimaryStorageExceptThis(final Connection conn, final String volumeUuid, final String avoidSnapshotUuid) {
        try {
            final VDI volume = getVDIbyUuid(conn, volumeUuid);
            if (volume == null) {
                throw new InternalErrorException("Could not destroy snapshot on volume " + volumeUuid + " due to can not find it");
            }
            // To avoid deleting snapshots which are still waiting in queue to get backed up.
            VDI avoidSnapshot = getVDIbyUuid(conn, avoidSnapshotUuid);
            if (avoidSnapshot == null) {
                throw new InternalErrorException("Could not find current snapshot " + avoidSnapshotUuid);
            }
            final Set<VDI> snapshots = volume.getSnapshots(conn);
            for (final VDI snapshot : snapshots) {
                try {
                    if (!snapshot.getUuid(conn).equals(avoidSnapshotUuid) && snapshot.getSnapshotTime(conn).before(avoidSnapshot.getSnapshotTime(conn))) {
                        snapshot.destroy(conn);
                    }
                } catch (final Exception e) {
                    final String msg = "Destroying snapshot: " + snapshot + " on primary storage failed due to " + e.toString();
                    s_logger.warn(msg, e);
                }
            }
            s_logger.debug("Successfully destroyed snapshot on volume: " + volumeUuid + " execept this current snapshot " + avoidSnapshotUuid);
            return true;
        } catch (final XenAPIException e) {
            final String msg = "Destroying snapshot on volume: " + volumeUuid + " execept this current snapshot " + avoidSnapshotUuid + " failed due to " + e.toString();
            s_logger.error(msg, e);
        } catch (final Exception e) {
            final String msg = "Destroying snapshot on volume: " + volumeUuid + " execept this current snapshot " + avoidSnapshotUuid + " failed due to " + e.toString();
            s_logger.warn(msg, e);
        }

        return false;
    }

    private boolean destroySnapshotOnPrimaryStorage(final Connection conn, final String lastSnapshotUuid) {
        try {
            final VDI snapshot = getVDIbyUuid(conn, lastSnapshotUuid);
            if (snapshot == null) {
                // since this is just used to cleanup leftover bad snapshots, no need to throw exception
                s_logger.warn("Could not destroy snapshot " + lastSnapshotUuid + " due to can not find it");
                return false;
            }
            snapshot.destroy(conn);
            return true;
        } catch (final XenAPIException e) {
            final String msg = "Destroying snapshot: " + lastSnapshotUuid + " failed due to " + e.toString();
            s_logger.error(msg, e);
        } catch (final Exception e) {
            final String msg = "Destroying snapshot: " + lastSnapshotUuid + " failed due to " + e.toString();
            s_logger.warn(msg, e);
        }
        return false;
    }

    @Override
    public Answer backupSnapshot(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final DataTO srcData = cmd.getSrcTO();
        final DataTO cacheData = cmd.getCacheTO();
        final DataTO destData = cmd.getDestTO();
        final int wait = cmd.getWait();
        final String primaryStorageNameLabel = srcData.getDataStore().getUuid();
        String secondaryStorageUrl = null;
        NfsTO cacheStore = null;
        String destPath = null;
        if (cacheData != null) {
            cacheStore = (NfsTO) cacheData.getDataStore();
            secondaryStorageUrl = cacheStore.getUrl();
            destPath = cacheData.getPath();
        } else {
            cacheStore = (NfsTO) destData.getDataStore();
            secondaryStorageUrl = cacheStore.getUrl();
            destPath = destData.getPath();
        }

        final SnapshotObjectTO snapshotTO = (SnapshotObjectTO) srcData;
        final SnapshotObjectTO snapshotOnImage = (SnapshotObjectTO) destData;
        final String snapshotUuid = snapshotTO.getPath();
        final String volumeUuid = snapshotTO.getVolume().getPath();

        final String prevBackupUuid = snapshotOnImage.getParentSnapshotPath();
        final String prevSnapshotUuid = snapshotTO.getParentSnapshotPath();

        // By default assume failure
        String details = null;
        String snapshotBackupUuid = null;
        Long physicalSize = null;
        final Map<String, String> options = cmd.getOptions();
        boolean fullbackup = Boolean.parseBoolean(options.get("fullSnapshot"));
        boolean result = false;
        try {
            final SR primaryStorageSR = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
            if (primaryStorageSR == null) {
                throw new InternalErrorException("Could not backup snapshot because the primary Storage SR could not be created from the name label: " +
                        primaryStorageNameLabel);
            }
            final String psUuid = primaryStorageSR.getUuid(conn);
            final Boolean isISCSI = IsISCSI(primaryStorageSR.getType(conn));

            final VDI snapshotVdi = getVDIbyUuid(conn, snapshotUuid);
            String snapshotPaUuid = null;

            if (prevSnapshotUuid != null && !fullbackup) {
                try {
                    snapshotPaUuid = getVhdParent(conn, psUuid, snapshotUuid, isISCSI);
                    if (snapshotPaUuid != null) {
                        final String snashotPaPaPaUuid = getVhdParent(conn, psUuid, snapshotPaUuid, isISCSI);
                        final String prevSnashotPaUuid = getVhdParent(conn, psUuid, prevSnapshotUuid, isISCSI);
                        if (snashotPaPaPaUuid != null && prevSnashotPaUuid != null && prevSnashotPaUuid.equals(snashotPaPaPaUuid)) {
                            fullbackup = false;
                        } else {
                            fullbackup = true;
                        }
                    }
                } catch (final Exception e) {
                    s_logger.debug("Failed to get parent snapshots, take full snapshot", e);
                    fullbackup = true;
                }
            }

            final URI uri = new URI(secondaryStorageUrl);
            final String secondaryStorageMountPath = uri.getHost() + ":" + uri.getPath();
            final DataStoreTO destStore = destData.getDataStore();
            final String folder = destPath;
            String finalPath = null;

            final String localMountPoint = BaseMountPointOnHost + File.separator + UUID.nameUUIDFromBytes(secondaryStorageUrl.getBytes()).toString();
            if (fullbackup) {
                // the first snapshot is always a full snapshot

                if (!hypervisorResource.createSecondaryStorageFolder(conn, secondaryStorageMountPath, folder)) {
                    details = " Filed to create folder " + folder + " in secondary storage";
                    s_logger.warn(details);
                    return new CopyCmdAnswer(details);
                }
                final String snapshotMountpoint = secondaryStorageUrl + "/" + folder;
                SR snapshotSr = null;
                try {
                    snapshotSr = hypervisorResource.createNfsSRbyURI(conn, new URI(snapshotMountpoint), false);
                    final VDI backedVdi = hypervisorResource.cloudVDIcopy(conn, snapshotVdi, snapshotSr, wait);
                    snapshotBackupUuid = backedVdi.getUuid(conn);
                    final String primarySRuuid = snapshotSr.getUuid(conn);
                    physicalSize = getSnapshotSize(conn, primarySRuuid, snapshotBackupUuid, isISCSI, wait);

                    if (destStore instanceof SwiftTO) {
                        try {
                            final String container = "S-" + snapshotTO.getVolume().getVolumeId().toString();
                            final String destSnapshotName = swiftBackupSnapshot(conn, (SwiftTO) destStore, snapshotSr.getUuid(conn), snapshotBackupUuid, container, false, wait);
                            final String swiftPath = container + File.separator + destSnapshotName;
                            finalPath = swiftPath;
                        } finally {
                            try {
                                deleteSnapshotBackup(conn, localMountPoint, folder, secondaryStorageMountPath, snapshotBackupUuid);
                            } catch (final Exception e) {
                                s_logger.debug("Failed to delete snapshot on cache storages", e);
                            }
                        }

                    } else if (destStore instanceof S3TO) {
                        try {
                            finalPath = backupSnapshotToS3(conn, (S3TO) destStore, snapshotSr.getUuid(conn), folder, snapshotBackupUuid, isISCSI, wait);
                            if (finalPath == null) {
                                throw new CloudRuntimeException("S3 upload of snapshots " + snapshotBackupUuid + " failed");
                            }
                        } finally {
                            try {
                                deleteSnapshotBackup(conn, localMountPoint, folder, secondaryStorageMountPath, snapshotBackupUuid);
                            } catch (final Exception e) {
                                s_logger.debug("Failed to delete snapshot on cache storages", e);
                            }
                        }
                        // finalPath = folder + File.separator + snapshotBackupUuid;
                    } else {
                        finalPath = folder + cacheStore.getPathSeparator() + snapshotBackupUuid;
                    }

                } finally {
                    if (snapshotSr != null) {
                        hypervisorResource.removeSR(conn, snapshotSr);
                    }
                }
            } else {
                final String primaryStorageSRUuid = primaryStorageSR.getUuid(conn);
                if (destStore instanceof SwiftTO) {
                    final String container = "S-" + snapshotTO.getVolume().getVolumeId().toString();
                    snapshotBackupUuid =
                            swiftBackupSnapshot(conn, (SwiftTO) destStore, primaryStorageSRUuid, snapshotPaUuid, "S-" + snapshotTO.getVolume().getVolumeId().toString(),
                                    isISCSI, wait);
                    finalPath = container + File.separator + snapshotBackupUuid;
                } else if (destStore instanceof S3TO) {
                    finalPath = backupSnapshotToS3(conn, (S3TO) destStore, primaryStorageSRUuid, folder, snapshotPaUuid, isISCSI, wait);
                    if (finalPath == null) {
                        throw new CloudRuntimeException("S3 upload of snapshots " + snapshotPaUuid + " failed");
                    }
                } else {
                    final String results =
                            backupSnapshot(conn, primaryStorageSRUuid, localMountPoint, folder, secondaryStorageMountPath, snapshotUuid, prevBackupUuid, isISCSI, wait);

                    final String[] tmp = results.split("#");
                    snapshotBackupUuid = tmp[1];
                    physicalSize = Long.parseLong(tmp[2]);
                    finalPath = folder + cacheStore.getPathSeparator() + snapshotBackupUuid;
                }
            }
            // delete primary snapshots with only the last one left
            destroySnapshotOnPrimaryStorageExceptThis(conn, volumeUuid, snapshotUuid);

            final SnapshotObjectTO newSnapshot = new SnapshotObjectTO();
            newSnapshot.setPath(finalPath);
            newSnapshot.setPhysicalSize(physicalSize);
            if (fullbackup) {
                newSnapshot.setParentSnapshotPath(null);
            } else {
                newSnapshot.setParentSnapshotPath(prevBackupUuid);
            }
            result = true;
            return new CopyCmdAnswer(newSnapshot);
        } catch (final XenAPIException e) {
            details = "BackupSnapshot Failed due to " + e.toString();
            s_logger.warn(details, e);
        } catch (final Exception e) {
            details = "BackupSnapshot Failed due to " + e.getMessage();
            s_logger.warn(details, e);
        } finally {
            if (!result) {
                // remove last bad primary snapshot when exception happens
                try {
                    destroySnapshotOnPrimaryStorage(conn, snapshotUuid);
                } catch (final Exception e) {
                    s_logger.debug("clean up snapshot failed", e);
                }
            }
        }

        return new CopyCmdAnswer(details);
    }

    @Override
    public Answer createTemplateFromVolume(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final VolumeObjectTO volume = (VolumeObjectTO) cmd.getSrcTO();
        final TemplateObjectTO template = (TemplateObjectTO) cmd.getDestTO();
        final NfsTO destStore = (NfsTO) cmd.getDestTO().getDataStore();
        final int wait = cmd.getWait();

        final String secondaryStoragePoolURL = destStore.getUrl();
        final String volumeUUID = volume.getPath();

        final String userSpecifiedName = template.getName();

        String details = null;
        SR tmpltSR = null;
        boolean result = false;
        String secondaryStorageMountPath = null;
        String installPath = null;
        try {
            final URI uri = new URI(secondaryStoragePoolURL);
            secondaryStorageMountPath = uri.getHost() + ":" + uri.getPath();
            installPath = template.getPath();
            if (!hypervisorResource.createSecondaryStorageFolder(conn, secondaryStorageMountPath, installPath)) {
                details = " Filed to create folder " + installPath + " in secondary storage";
                s_logger.warn(details);
                return new CopyCmdAnswer(details);
            }

            final VDI vol = getVDIbyUuid(conn, volumeUUID);
            // create template SR
            final URI tmpltURI = new URI(secondaryStoragePoolURL + "/" + installPath);
            tmpltSR = hypervisorResource.createNfsSRbyURI(conn, tmpltURI, false);

            // copy volume to template SR
            final VDI tmpltVDI = hypervisorResource.cloudVDIcopy(conn, vol, tmpltSR, wait);
            // scan makes XenServer pick up VDI physicalSize
            tmpltSR.scan(conn);
            if (userSpecifiedName != null) {
                tmpltVDI.setNameLabel(conn, userSpecifiedName);
            }

            final String tmpltUUID = tmpltVDI.getUuid(conn);
            final String tmpltFilename = tmpltUUID + ".vhd";
            final long virtualSize = tmpltVDI.getVirtualSize(conn);
            final long physicalSize = tmpltVDI.getPhysicalUtilisation(conn);
            // create the template.properties file
            final String templatePath = secondaryStorageMountPath + "/" + installPath;
            result =
                    hypervisorResource.postCreatePrivateTemplate(conn, templatePath, tmpltFilename, tmpltUUID, userSpecifiedName, null, physicalSize, virtualSize,
                            template.getId());
            if (!result) {
                throw new CloudRuntimeException("Could not create the template.properties file on secondary storage dir: " + tmpltURI);
            }
            installPath = installPath + "/" + tmpltFilename;
            hypervisorResource.removeSR(conn, tmpltSR);
            tmpltSR = null;
            final TemplateObjectTO newTemplate = new TemplateObjectTO();
            newTemplate.setPath(installPath);
            newTemplate.setFormat(ImageFormat.VHD);
            newTemplate.setSize(virtualSize);
            newTemplate.setPhysicalSize(physicalSize);
            newTemplate.setName(tmpltUUID);
            final CopyCmdAnswer answer = new CopyCmdAnswer(newTemplate);
            return answer;
        } catch (final Exception e) {
            if (tmpltSR != null) {
                hypervisorResource.removeSR(conn, tmpltSR);
            }
            if (secondaryStorageMountPath != null) {
                hypervisorResource.deleteSecondaryStorageFolder(conn, secondaryStorageMountPath, installPath);
            }
            details = "Creating template from volume " + volumeUUID + " failed due to " + e.toString();
            s_logger.error(details, e);
        }
        return new CopyCmdAnswer(details);
    }

    @Override
    public Answer createTemplateFromSnapshot(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();

        final SnapshotObjectTO snapshotObjTO = (SnapshotObjectTO)cmd.getSrcTO();
        final TemplateObjectTO templateObjTO = (TemplateObjectTO)cmd.getDestTO();

        if (!(snapshotObjTO.getDataStore() instanceof PrimaryDataStoreTO) || !(templateObjTO.getDataStore() instanceof NfsTO)) {
            return null;
        }

        final String userSpecifiedTemplateName = templateObjTO.getName();

        NfsTO destStore = null;
        URI destUri = null;

        try {
            destStore = (NfsTO)templateObjTO.getDataStore();

            destUri = new URI(destStore.getUrl());
        } catch (final Exception ex) {
            s_logger.debug("Invalid URI", ex);

            return new CopyCmdAnswer("Invalid URI: " + ex.toString());
        }

        SR srcSr = null;
        SR destSr = null;

        final String destDir = templateObjTO.getPath();
        VDI destVdi = null;

        boolean result = false;

        try {
            final Map<String, String> srcDetails = cmd.getOptions();

            final String iScsiName = srcDetails.get(DiskTO.IQN);
            final String storageHost = srcDetails.get(DiskTO.STORAGE_HOST);
            final String chapInitiatorUsername = srcDetails.get(DiskTO.CHAP_INITIATOR_USERNAME);
            final String chapInitiatorSecret = srcDetails.get(DiskTO.CHAP_INITIATOR_SECRET);

            srcSr = hypervisorResource.getIscsiSR(conn, iScsiName, storageHost, iScsiName, chapInitiatorUsername, chapInitiatorSecret, true);

            final String destNfsPath = destUri.getHost() + ":" + destUri.getPath();

            if (!hypervisorResource.createSecondaryStorageFolder(conn, destNfsPath, destDir)) {
                final String details = " Failed to create folder " + destDir + " in secondary storage";

                s_logger.warn(details);

                return new CopyCmdAnswer(details);
            }

            final URI templateUri = new URI(destStore.getUrl() + "/" + destDir);

            destSr = hypervisorResource.createNfsSRbyURI(conn, templateUri, false);

            // there should only be one VDI in this SR
            final VDI srcVdi = srcSr.getVDIs(conn).iterator().next();

            destVdi = srcVdi.copy(conn, destSr);

            // scan makes XenServer pick up VDI physicalSize
            destSr.scan(conn);

            if (userSpecifiedTemplateName != null) {
                destVdi.setNameLabel(conn, userSpecifiedTemplateName);
            }

            final String templateUuid = destVdi.getUuid(conn);
            final String templateFilename = templateUuid + ".vhd";
            final long virtualSize = destVdi.getVirtualSize(conn);
            final long physicalSize = destVdi.getPhysicalUtilisation(conn);

            // create the template.properties file
            String templatePath = destNfsPath + "/" + destDir;

            templatePath = templatePath.replaceAll("//", "/");

            result = hypervisorResource.postCreatePrivateTemplate(conn, templatePath, templateFilename, templateUuid, userSpecifiedTemplateName, null,
                    physicalSize, virtualSize, templateObjTO.getId());

            if (!result) {
                throw new CloudRuntimeException("Could not create the template.properties file on secondary storage dir: " + templateUri);
            }

            final TemplateObjectTO newTemplate = new TemplateObjectTO();

            newTemplate.setPath(destDir + "/" + templateFilename);
            newTemplate.setFormat(Storage.ImageFormat.VHD);
            newTemplate.setHypervisorType(HypervisorType.XenServer);
            newTemplate.setSize(virtualSize);
            newTemplate.setPhysicalSize(physicalSize);
            newTemplate.setName(templateUuid);

            result = true;

            return new CopyCmdAnswer(newTemplate);
        } catch (final Exception ex) {
            s_logger.error("Failed to create a template from a snapshot", ex);

            return new CopyCmdAnswer("Failed to create a template from a snapshot: " + ex.toString());
        } finally {
            if (!result) {
                if (destVdi != null) {
                    try {
                        destVdi.destroy(conn);
                    } catch (final Exception e) {
                        s_logger.debug("Cleaned up leftover VDI on destination storage due to failure: ", e);
                    }
                }
            }

            if (srcSr != null) {
                hypervisorResource.removeSR(conn, srcSr);
            }

            if (destSr != null) {
                hypervisorResource.removeSR(conn, destSr);
            }
        }
    }

    @Override
    public Answer createVolumeFromSnapshot(final CopyCommand cmd) {
        final Connection conn = hypervisorResource.getConnection();
        final DataTO srcData = cmd.getSrcTO();
        final SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData;
        final DataTO destData = cmd.getDestTO();
        final DataStoreTO imageStore = srcData.getDataStore();

        if (srcData.getDataStore() instanceof PrimaryDataStoreTO && destData.getDataStore() instanceof PrimaryDataStoreTO) {
            return createVolumeFromSnapshot2(cmd);
        }

        if (!(imageStore instanceof NfsTO)) {
            return new CopyCmdAnswer("unsupported protocol");
        }

        final NfsTO nfsImageStore = (NfsTO) imageStore;
        final String primaryStorageNameLabel = destData.getDataStore().getUuid();
        final String secondaryStorageUrl = nfsImageStore.getUrl();
        final int wait = cmd.getWait();
        boolean result = false;
        // Generic error message.
        String details = null;
        String volumeUUID = null;

        if (secondaryStorageUrl == null) {
            details += " because the URL passed: " + secondaryStorageUrl + " is invalid.";
            return new CopyCmdAnswer(details);
        }
        try {
            final SR primaryStorageSR = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
            if (primaryStorageSR == null) {
                throw new InternalErrorException("Could not create volume from snapshot because the primary Storage SR could not be created from the name label: " +
                        primaryStorageNameLabel);
            }
            // Get the absolute path of the snapshot on the secondary storage.
            String snapshotInstallPath = snapshot.getPath();
            final int index = snapshotInstallPath.lastIndexOf(nfsImageStore.getPathSeparator());
            final String snapshotName = snapshotInstallPath.substring(index + 1);

            if (!snapshotName.startsWith("VHD-") && !snapshotName.endsWith(".vhd")) {
                snapshotInstallPath = snapshotInstallPath + ".vhd";
            }
            final URI snapshotURI = new URI(secondaryStorageUrl + nfsImageStore.getPathSeparator() + snapshotInstallPath);
            final String snapshotPath = snapshotURI.getHost() + ":" + snapshotURI.getPath();
            final String srUuid = primaryStorageSR.getUuid(conn);
            volumeUUID = copy_vhd_from_secondarystorage(conn, snapshotPath, srUuid, wait);
            result = true;
            final VDI volume = VDI.getByUuid(conn, volumeUUID);
            final VDI.Record vdir = volume.getRecord(conn);
            final VolumeObjectTO newVol = new VolumeObjectTO();
            newVol.setPath(volumeUUID);
            newVol.setSize(vdir.virtualSize);
            return new CopyCmdAnswer(newVol);
        } catch (final XenAPIException e) {
            details += " due to " + e.toString();
            s_logger.warn(details, e);
        } catch (final Exception e) {
            details += " due to " + e.getMessage();
            s_logger.warn(details, e);
        }
        if (!result) {
            // Is this logged at a higher level?
            s_logger.error(details);
        }

        // In all cases return something.
        return new CopyCmdAnswer(details);
    }

    protected Answer createVolumeFromSnapshot2(final CopyCommand cmd) {
        try {
            final Connection conn = hypervisorResource.getConnection();

            final Map<String, String> srcOptions = cmd.getOptions();

            final String src_iScsiName = srcOptions.get(DiskTO.IQN);
            final String srcStorageHost = srcOptions.get(DiskTO.STORAGE_HOST);
            final String srcChapInitiatorUsername = srcOptions.get(DiskTO.CHAP_INITIATOR_USERNAME);
            final String srcChapInitiatorSecret = srcOptions.get(DiskTO.CHAP_INITIATOR_SECRET);

            final SR srcSr = hypervisorResource.getIscsiSR(conn, src_iScsiName, srcStorageHost, src_iScsiName, srcChapInitiatorUsername, srcChapInitiatorSecret, false);

            final Map<String, String> destOptions = cmd.getOptions2();

            final String dest_iScsiName = destOptions.get(DiskTO.IQN);
            final String destStorageHost = destOptions.get(DiskTO.STORAGE_HOST);
            final String destChapInitiatorUsername = destOptions.get(DiskTO.CHAP_INITIATOR_USERNAME);
            final String destChapInitiatorSecret = destOptions.get(DiskTO.CHAP_INITIATOR_SECRET);

            final SR destSr = hypervisorResource.getIscsiSR(conn, dest_iScsiName, destStorageHost, dest_iScsiName, destChapInitiatorUsername, destChapInitiatorSecret, false);

            // there should only be one VDI in this SR
            final VDI srcVdi = srcSr.getVDIs(conn).iterator().next();

            final VDI vdiCopy = srcVdi.copy(conn, destSr);

            final VolumeObjectTO newVol = new VolumeObjectTO();

            newVol.setSize(vdiCopy.getVirtualSize(conn));
            newVol.setPath(vdiCopy.getUuid(conn));
            newVol.setFormat(ImageFormat.VHD);

            hypervisorResource.removeSR(conn, srcSr);
            hypervisorResource.removeSR(conn, destSr);

            return new CopyCmdAnswer(newVol);
        }
        catch (final Exception ex) {
            s_logger.warn("Failed to copy snapshot to volume: " + ex.toString(), ex);

            return new CopyCmdAnswer(ex.getMessage());
        }
    }

    @Override
    public Answer deleteSnapshot(final DeleteCommand cmd) {
        final SnapshotObjectTO snapshot = (SnapshotObjectTO) cmd.getData();
        final DataStoreTO store = snapshot.getDataStore();
        if (store.getRole() == DataStoreRole.Primary) {
            final Connection conn = hypervisorResource.getConnection();
            final VDI snapshotVdi = getVDIbyUuid(conn, snapshot.getPath());
            if (snapshotVdi == null) {
                return new Answer(null);
            }
            String errMsg = null;
            try {
                deleteVDI(conn, snapshotVdi);
            } catch (final BadServerResponse e) {
                s_logger.debug("delete snapshot failed:" + e.toString());
                errMsg = e.toString();
            } catch (final XenAPIException e) {
                s_logger.debug("delete snapshot failed:" + e.toString());
                errMsg = e.toString();
            } catch (final XmlRpcException e) {
                s_logger.debug("delete snapshot failed:" + e.toString());
                errMsg = e.toString();
            }
            return new Answer(cmd, false, errMsg);
        }
        return new Answer(cmd, false, "unsupported storage type");
    }

    @Override
    public Answer introduceObject(final IntroduceObjectCmd cmd) {
        try {
            final Connection conn = hypervisorResource.getConnection();
            final DataStoreTO store = cmd.getDataTO().getDataStore();
            final SR poolSr = hypervisorResource.getStorageRepository(conn, store.getUuid());
            poolSr.scan(conn);
            return new IntroduceObjectAnswer(cmd.getDataTO());
        } catch (final Exception e) {
            s_logger.debug("Failed to introduce object", e);
            return new Answer(cmd, false, e.toString());
        }
    }

    @Override
    public Answer forgetObject(final ForgetObjectCmd cmd) {
        try {
            final Connection conn = hypervisorResource.getConnection();
            final DataTO data = cmd.getDataTO();
            final VDI vdi = VDI.getByUuid(conn, data.getPath());
            vdi.forget(conn);
            return new IntroduceObjectAnswer(cmd.getDataTO());
        } catch (final Exception e) {
            s_logger.debug("Failed to introduce object", e);
            return new Answer(cmd, false, e.toString());
        }
    }
}
