com.cloud.hypervisor.vmware.mo.VirtualMachineMO.java Source code

Java tutorial

Introduction

Here is the source code for com.cloud.hypervisor.vmware.mo.VirtualMachineMO.java

Source

// 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.vmware.mo;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.vmware.vim25.ArrayOfManagedObjectReference;
import com.vmware.vim25.ChoiceOption;
import com.vmware.vim25.CustomFieldStringValue;
import com.vmware.vim25.DistributedVirtualSwitchPortConnection;
import com.vmware.vim25.DynamicProperty;
import com.vmware.vim25.ElementDescription;
import com.vmware.vim25.GuestInfo;
import com.vmware.vim25.GuestOsDescriptor;
import com.vmware.vim25.HttpNfcLeaseDeviceUrl;
import com.vmware.vim25.HttpNfcLeaseInfo;
import com.vmware.vim25.HttpNfcLeaseState;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.ObjectContent;
import com.vmware.vim25.ObjectSpec;
import com.vmware.vim25.OptionValue;
import com.vmware.vim25.OvfCreateDescriptorParams;
import com.vmware.vim25.OvfCreateDescriptorResult;
import com.vmware.vim25.OvfFile;
import com.vmware.vim25.PropertyFilterSpec;
import com.vmware.vim25.PropertySpec;
import com.vmware.vim25.TraversalSpec;
import com.vmware.vim25.VirtualCdrom;
import com.vmware.vim25.VirtualCdromIsoBackingInfo;
import com.vmware.vim25.VirtualCdromRemotePassthroughBackingInfo;
import com.vmware.vim25.VirtualDevice;
import com.vmware.vim25.VirtualDeviceBackingInfo;
import com.vmware.vim25.VirtualDeviceConfigSpec;
import com.vmware.vim25.VirtualDeviceConfigSpecFileOperation;
import com.vmware.vim25.VirtualDeviceConfigSpecOperation;
import com.vmware.vim25.VirtualDeviceConnectInfo;
import com.vmware.vim25.VirtualDisk;
import com.vmware.vim25.VirtualDiskFlatVer1BackingInfo;
import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
import com.vmware.vim25.VirtualDiskMode;
import com.vmware.vim25.VirtualDiskRawDiskMappingVer1BackingInfo;
import com.vmware.vim25.VirtualDiskSparseVer1BackingInfo;
import com.vmware.vim25.VirtualDiskSparseVer2BackingInfo;
import com.vmware.vim25.VirtualDiskType;
import com.vmware.vim25.VirtualEthernetCard;
import com.vmware.vim25.VirtualEthernetCardDistributedVirtualPortBackingInfo;
import com.vmware.vim25.VirtualHardwareOption;
import com.vmware.vim25.VirtualIDEController;
import com.vmware.vim25.VirtualLsiLogicController;
import com.vmware.vim25.VirtualMachineCloneSpec;
import com.vmware.vim25.VirtualMachineConfigInfo;
import com.vmware.vim25.VirtualMachineConfigOption;
import com.vmware.vim25.VirtualMachineConfigSpec;
import com.vmware.vim25.VirtualMachineConfigSummary;
import com.vmware.vim25.VirtualMachineFileInfo;
import com.vmware.vim25.VirtualMachineMessage;
import com.vmware.vim25.VirtualMachineMovePriority;
import com.vmware.vim25.VirtualMachinePowerState;
import com.vmware.vim25.VirtualMachineQuestionInfo;
import com.vmware.vim25.VirtualMachineRelocateDiskMoveOptions;
import com.vmware.vim25.VirtualMachineRelocateSpec;
import com.vmware.vim25.VirtualMachineRelocateSpecDiskLocator;
import com.vmware.vim25.VirtualMachineRuntimeInfo;
import com.vmware.vim25.VirtualMachineSnapshotInfo;
import com.vmware.vim25.VirtualMachineSnapshotTree;
import com.vmware.vim25.VirtualPCIController;
import com.vmware.vim25.VirtualSCSIController;
import com.vmware.vim25.VirtualSCSISharing;

import com.cloud.hypervisor.vmware.mo.SnapshotDescriptor.SnapshotInfo;
import com.cloud.hypervisor.vmware.util.VmwareContext;
import com.cloud.hypervisor.vmware.util.VmwareHelper;
import com.cloud.utils.ActionDelegate;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.script.Script;

public class VirtualMachineMO extends BaseMO {
    private static final Logger s_logger = Logger.getLogger(VirtualMachineMO.class);
    private static final ExecutorService MonitorServiceExecutor = Executors
            .newCachedThreadPool(new NamedThreadFactory("VM-Question-Monitor"));
    private ManagedObjectReference _vmEnvironmentBrowser = null;

    public VirtualMachineMO(VmwareContext context, ManagedObjectReference morVm) {
        super(context, morVm);
    }

    public VirtualMachineMO(VmwareContext context, String morType, String morValue) {
        super(context, morType, morValue);
    }

    public Pair<DatacenterMO, String> getOwnerDatacenter() throws Exception {
        return DatacenterMO.getOwnerDatacenter(getContext(), getMor());
    }

    public Pair<DatastoreMO, String> getOwnerDatastore(String dsFullPath) throws Exception {
        String dsName = DatastoreFile.getDatastoreNameFromPath(dsFullPath);

        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("Datastore");
        pSpec.getPathSet().add("name");

        TraversalSpec vmDatastoreTraversal = new TraversalSpec();
        vmDatastoreTraversal.setType("VirtualMachine");
        vmDatastoreTraversal.setPath("datastore");
        vmDatastoreTraversal.setName("vmDatastoreTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(vmDatastoreTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<PropertyFilterSpec>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(),
                pfSpecArr);

        if (ocs != null) {
            for (ObjectContent oc : ocs) {
                DynamicProperty prop = oc.getPropSet().get(0);
                if (prop.getVal().toString().equals(dsName)) {
                    return new Pair<DatastoreMO, String>(new DatastoreMO(_context, oc.getObj()), dsName);
                }
            }
        }

        return null;
    }

    public HostMO getRunningHost() throws Exception {
        VirtualMachineRuntimeInfo runtimeInfo = getRuntimeInfo();
        return new HostMO(_context, runtimeInfo.getHost());
    }

    public String getVmName() throws Exception {
        return (String) getContext().getVimClient().getDynamicProperty(_mor, "name");
    }

    public GuestInfo getVmGuestInfo() throws Exception {
        return (GuestInfo) getContext().getVimClient().getDynamicProperty(_mor, "guest");
    }

    public void answerVM(String questionId, String choice) throws Exception {
        getContext().getService().answerVM(_mor, questionId, choice);
    }

    public boolean isVMwareToolsRunning() throws Exception {
        GuestInfo guestInfo = getVmGuestInfo();
        if (guestInfo != null) {
            if ("guestToolsRunning".equalsIgnoreCase(guestInfo.getToolsRunningStatus()))
                return true;
        }
        return false;
    }

    public boolean powerOn() throws Exception {
        if (getResetSafePowerState() == VirtualMachinePowerState.POWERED_ON)
            return true;

        ManagedObjectReference morTask = _context.getService().powerOnVMTask(_mor, null);
        // Monitor VM questions
        final Boolean[] flags = { false };
        final VirtualMachineMO vmMo = this;
        Future<?> future = MonitorServiceExecutor.submit(new Runnable() {
            @Override
            public void run() {
                s_logger.info("VM Question monitor started...");

                while (!flags[0]) {
                    try {
                        VirtualMachineRuntimeInfo runtimeInfo = vmMo.getRuntimeInfo();
                        VirtualMachineQuestionInfo question = runtimeInfo.getQuestion();
                        if (question != null) {
                            s_logger.info("Question id: " + question.getId());
                            s_logger.info("Question text: " + question.getText());
                            if (question.getMessage() != null) {
                                for (VirtualMachineMessage msg : question.getMessage()) {
                                    if (s_logger.isInfoEnabled()) {
                                        s_logger.info("msg id: " + msg.getId());
                                        s_logger.info("msg text: " + msg.getText());
                                    }
                                    if ("msg.uuid.altered".equalsIgnoreCase(msg.getId())) {
                                        s_logger.info(
                                                "Found that VM has a pending question that we need to answer programmatically, question id: "
                                                        + msg.getId()
                                                        + ", we will automatically answer as 'moved it' to address out of band HA for the VM");
                                        vmMo.answerVM(question.getId(), "1");
                                        break;
                                    }
                                }
                            }

                            if (s_logger.isTraceEnabled())
                                s_logger.trace("These are the choices we can have just in case");
                            ChoiceOption choice = question.getChoice();
                            if (choice != null) {
                                for (ElementDescription info : choice.getChoiceInfo()) {
                                    if (s_logger.isTraceEnabled()) {
                                        s_logger.trace("Choice option key: " + info.getKey());
                                        s_logger.trace("Choice option label: " + info.getLabel());
                                    }
                                }
                            }
                        }
                    } catch (Throwable e) {
                        s_logger.error("Unexpected exception: ", e);
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                s_logger.info("VM Question monitor stopped");
            }
        });

        try {
            boolean result = _context.getVimClient().waitForTask(morTask);
            if (result) {
                _context.waitForTaskProgressDone(morTask);
                return true;
            } else {
                s_logger.error(
                        "VMware powerOnVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
            }
        } finally {
            // make sure to let VM question monitor exit
            flags[0] = true;
        }

        return false;
    }

    public boolean powerOff() throws Exception {
        if (getResetSafePowerState() == VirtualMachinePowerState.POWERED_OFF)
            return true;

        return powerOffNoCheck();
    }

    public boolean safePowerOff(int shutdownWaitMs) throws Exception {

        if (getResetSafePowerState() == VirtualMachinePowerState.POWERED_OFF)
            return true;

        if (isVMwareToolsRunning()) {
            try {
                String vmName = getName();

                s_logger.info("Try gracefully shut down VM " + vmName);
                shutdown();

                long startTick = System.currentTimeMillis();
                while (getResetSafePowerState() != VirtualMachinePowerState.POWERED_OFF
                        && System.currentTimeMillis() - startTick < shutdownWaitMs) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }

                if (getResetSafePowerState() != VirtualMachinePowerState.POWERED_OFF) {
                    s_logger.info("can not gracefully shutdown VM within " + (shutdownWaitMs / 1000)
                            + " seconds, we will perform force power off on VM " + vmName);
                    return powerOffNoCheck();
                }

                return true;
            } catch (Exception e) {
                s_logger.warn(
                        "Failed to do guest-os graceful shutdown due to " + VmwareHelper.getExceptionMessage(e));
            }
        }

        return powerOffNoCheck();
    }

    private boolean powerOffNoCheck() throws Exception {
        ManagedObjectReference morTask = _context.getService().powerOffVMTask(_mor);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);

            // It seems that even if a power-off task is returned done, VM state may still not be marked,
            // wait up to 5 seconds to make sure to avoid race conditioning for immediate following on operations
            // that relies on a powered-off VM
            long startTick = System.currentTimeMillis();
            while (getResetSafePowerState() != VirtualMachinePowerState.POWERED_OFF
                    && System.currentTimeMillis() - startTick < 5000) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
            return true;
        } else {
            if (getResetSafePowerState() == VirtualMachinePowerState.POWERED_OFF) {
                // to help deal with possible race-condition
                s_logger.info(
                        "Current power-off task failed. However, VM has been switched to the state we are expecting for");
                return true;
            }

            s_logger.error("VMware powerOffVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public VirtualMachinePowerState getResetSafePowerState() throws Exception {

        VirtualMachinePowerState powerState = VirtualMachinePowerState.POWERED_OFF;

        // This is really ugly, there is a case that when windows guest VM is doing sysprep, the temporary
        // rebooting process may let us pick up a "poweredOff" state during VMsync process, this can trigger
        // a series actions. Unfortunately, from VMware API we can not distinguish power state into such details.
        // We hope by giving it 3 second to re-read the state can cover this as a short-term solution.
        //
        // In the future, VMsync should not kick off CloudStack action (this is not a HA case) based on VM
        // state report, until then we can remove this hacking fix
        for (int i = 0; i < 3; i++) {
            powerState = (VirtualMachinePowerState) getContext().getVimClient().getDynamicProperty(_mor,
                    "runtime.powerState");
            if (powerState == VirtualMachinePowerState.POWERED_OFF) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            } else {
                break;
            }
        }

        return powerState;
    }

    public VirtualMachinePowerState getPowerState() throws Exception {
        return (VirtualMachinePowerState) getContext().getVimClient().getDynamicProperty(_mor,
                "runtime.powerState");
    }

    public boolean reset() throws Exception {
        ManagedObjectReference morTask = _context.getService().resetVMTask(_mor);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware resetVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        return false;
    }

    public void shutdown() throws Exception {
        _context.getService().shutdownGuest(_mor);
    }

    public void rebootGuest() throws Exception {
        _context.getService().rebootGuest(_mor);
    }

    public void markAsTemplate() throws Exception {
        _context.getService().markAsTemplate(_mor);
    }

    public boolean isTemplate() throws Exception {
        VirtualMachineConfigInfo configInfo = getConfigInfo();
        return configInfo.isTemplate();
    }

    public boolean migrate(ManagedObjectReference morRp, ManagedObjectReference morTargetHost) throws Exception {
        ManagedObjectReference morTask = _context.getService().migrateVMTask(_mor, morRp, morTargetHost,
                VirtualMachineMovePriority.DEFAULT_PRIORITY, null);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware migrateVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public boolean changeDatastore(VirtualMachineRelocateSpec relocateSpec) throws Exception {
        ManagedObjectReference morTask = _context.getVimClient().getService().relocateVMTask(_mor, relocateSpec,
                VirtualMachineMovePriority.DEFAULT_PRIORITY);
        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware RelocateVM_Task to change datastore failed due to "
                    + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        return false;
    }

    public boolean changeHost(VirtualMachineRelocateSpec relocateSpec) throws Exception {
        ManagedObjectReference morTask = _context.getService().relocateVMTask(_mor, relocateSpec,
                VirtualMachineMovePriority.DEFAULT_PRIORITY);
        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware RelocateVM_Task to change host failed due to "
                    + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        return false;
    }

    public boolean relocate(ManagedObjectReference morTargetHost) throws Exception {
        VirtualMachineRelocateSpec relocateSpec = new VirtualMachineRelocateSpec();
        relocateSpec.setHost(morTargetHost);

        ManagedObjectReference morTask = _context.getService().relocateVMTask(_mor, relocateSpec, null);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware relocateVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public VirtualMachineSnapshotInfo getSnapshotInfo() throws Exception {
        return (VirtualMachineSnapshotInfo) _context.getVimClient().getDynamicProperty(_mor, "snapshot");
    }

    public boolean createSnapshot(String snapshotName, String snapshotDescription, boolean dumpMemory,
            boolean quiesce) throws Exception {

        ManagedObjectReference morTask = _context.getService().createSnapshotTask(_mor, snapshotName,
                snapshotDescription, dumpMemory, quiesce);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);

            ManagedObjectReference morSnapshot = null;
            // We still need to wait until the object appear in vCenter
            long startTick = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTick < 10000) {
                morSnapshot = getSnapshotMor(snapshotName);
                if (morSnapshot != null) {
                    break;
                }

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

            if (morSnapshot == null)
                s_logger.error(
                        "We've been waiting for over 10 seconds for snapshot MOR to be appearing in vCenter after CreateSnapshot task is done, but it is still not there?!");

            return true;
        } else {
            s_logger.error(
                    "VMware createSnapshot_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public boolean removeSnapshot(String snapshotName, boolean removeChildren) throws Exception {
        ManagedObjectReference morSnapshot = getSnapshotMor(snapshotName);
        if (morSnapshot == null) {
            s_logger.warn("Unable to find snapshot: " + snapshotName);
            return false;
        }

        ManagedObjectReference morTask = _context.getService().removeSnapshotTask(morSnapshot, removeChildren,
                true);
        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error(
                    "VMware removeSnapshot_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public boolean revertToSnapshot(String snapshotName) throws Exception {
        ManagedObjectReference morSnapshot = getSnapshotMor(snapshotName);
        if (morSnapshot == null) {
            s_logger.warn("Unable to find snapshot: " + snapshotName);
            return false;
        }
        ManagedObjectReference morTask = _context.getService().revertToSnapshotTask(morSnapshot, _mor, null);
        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error(
                    "VMware revert to snapshot failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public boolean removeAllSnapshots() throws Exception {
        VirtualMachineSnapshotInfo snapshotInfo = getSnapshotInfo();

        if (snapshotInfo != null && snapshotInfo.getRootSnapshotList() != null) {
            List<VirtualMachineSnapshotTree> tree = snapshotInfo.getRootSnapshotList();
            for (VirtualMachineSnapshotTree treeNode : tree) {
                ManagedObjectReference morTask = _context.getService().removeSnapshotTask(treeNode.getSnapshot(),
                        true, true);
                boolean result = _context.getVimClient().waitForTask(morTask);
                if (result) {
                    _context.waitForTaskProgressDone(morTask);
                } else {
                    s_logger.error("VMware removeSnapshot_Task failed due to "
                            + TaskMO.getTaskFailureInfo(_context, morTask));
                    return false;
                }
            }
        }

        return true;
    }

    public String getSnapshotDiskFileDatastorePath(VirtualMachineFileInfo vmFileInfo,
            List<Pair<ManagedObjectReference, String>> datastoreMounts, String snapshotDiskFile) throws Exception {

        // if file path start with "/", need to search all datastore mounts on the host in order
        // to form fully qualified datastore path
        if (snapshotDiskFile.startsWith("/")) {
            for (Pair<ManagedObjectReference, String> mount : datastoreMounts) {
                if (snapshotDiskFile.startsWith(mount.second())) {
                    DatastoreMO dsMo = new DatastoreMO(_context, mount.first());

                    String dsFullPath = String.format("[%s] %s", dsMo.getName(),
                            snapshotDiskFile.substring(mount.second().length() + 1));
                    s_logger.info("Convert snapshot disk file name to datastore path. " + snapshotDiskFile + "->"
                            + dsFullPath);
                    return dsFullPath;
                }
            }

            s_logger.info("Convert snapshot disk file name to datastore path. " + snapshotDiskFile + "->"
                    + snapshotDiskFile);
            return snapshotDiskFile;
        } else {

            // snapshot directory string from VirtualMachineFileInfo ends with /
            String dsFullPath = vmFileInfo.getSnapshotDirectory() + snapshotDiskFile;
            s_logger.info(
                    "Convert snapshot disk file name to datastore path. " + snapshotDiskFile + "->" + dsFullPath);
            return dsFullPath;
        }
    }

    public SnapshotDescriptor getSnapshotDescriptor() throws Exception {

        Pair<DatacenterMO, String> dcPair = getOwnerDatacenter();

        String dsPath = getSnapshotDescriptorDatastorePath();
        assert (dsPath != null);
        String url = getContext().composeDatastoreBrowseUrl(dcPair.second(), dsPath);
        byte[] content = getContext().getResourceContent(url);

        if (content == null || content.length < 1) {
            s_logger.warn("Snapshot descriptor file (vsd) does not exist anymore?");
        }

        SnapshotDescriptor descriptor = new SnapshotDescriptor();
        descriptor.parse(content);
        return descriptor;
    }

    public String getSnapshotDescriptorDatastorePath() throws Exception {
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("VirtualMachine");
        pSpec.getPathSet().add("name");
        pSpec.getPathSet().add("config.files");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.FALSE);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<PropertyFilterSpec>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(),
                pfSpecArr);
        assert (ocs != null);

        String vmName = null;
        VirtualMachineFileInfo fileInfo = null;

        assert (ocs.size() == 1);
        for (ObjectContent oc : ocs) {
            List<DynamicProperty> props = oc.getPropSet();
            if (props != null) {
                assert (props.size() == 2);

                for (DynamicProperty prop : props) {
                    if (prop.getName().equals("name")) {
                        vmName = prop.getVal().toString();
                    } else {
                        fileInfo = (VirtualMachineFileInfo) prop.getVal();
                    }
                }
            }
        }
        assert (vmName != null);
        assert (fileInfo != null);

        // .vmsd file exists at the same directory of .vmx file
        DatastoreFile vmxFile = new DatastoreFile(fileInfo.getVmPathName());
        return vmxFile.getCompanionPath(vmName + ".vmsd");
    }

    public ManagedObjectReference getSnapshotMor(String snapshotName) throws Exception {
        VirtualMachineSnapshotInfo info = getSnapshotInfo();
        if (info != null) {
            List<VirtualMachineSnapshotTree> snapTree = info.getRootSnapshotList();
            return VmwareHelper.findSnapshotInTree(snapTree, snapshotName);
        }
        return null;
    }

    public boolean hasSnapshot() throws Exception {
        VirtualMachineSnapshotInfo info = getSnapshotInfo();
        if (info != null) {
            return info.getCurrentSnapshot() != null;
        }
        return false;
    }

    public boolean createFullClone(String cloneName, ManagedObjectReference morFolder,
            ManagedObjectReference morResourcePool, ManagedObjectReference morDs) throws Exception {

        VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec();
        VirtualMachineRelocateSpec relocSpec = new VirtualMachineRelocateSpec();
        cloneSpec.setLocation(relocSpec);
        cloneSpec.setPowerOn(false);
        cloneSpec.setTemplate(false);

        relocSpec.setDatastore(morDs);
        relocSpec.setPool(morResourcePool);
        ManagedObjectReference morTask = _context.getService().cloneVMTask(_mor, morFolder, cloneName, cloneSpec);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware cloneVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public boolean createLinkedClone(String cloneName, ManagedObjectReference morBaseSnapshot,
            ManagedObjectReference morFolder, ManagedObjectReference morResourcePool, ManagedObjectReference morDs)
            throws Exception {

        assert (morBaseSnapshot != null);
        assert (morFolder != null);
        assert (morResourcePool != null);
        assert (morDs != null);

        VirtualDisk[] independentDisks = getAllIndependentDiskDevice();
        VirtualMachineRelocateSpec rSpec = new VirtualMachineRelocateSpec();
        if (independentDisks.length > 0) {
            List<VirtualMachineRelocateSpecDiskLocator> diskLocator = new ArrayList<VirtualMachineRelocateSpecDiskLocator>(
                    independentDisks.length);
            for (int i = 0; i < independentDisks.length; i++) {
                VirtualMachineRelocateSpecDiskLocator loc = new VirtualMachineRelocateSpecDiskLocator();
                loc.setDatastore(morDs);
                loc.setDiskId(independentDisks[i].getKey());
                loc.setDiskMoveType(
                        VirtualMachineRelocateDiskMoveOptions.MOVE_ALL_DISK_BACKINGS_AND_DISALLOW_SHARING.value());
                diskLocator.add(loc);
            }

            rSpec.setDiskMoveType(VirtualMachineRelocateDiskMoveOptions.CREATE_NEW_CHILD_DISK_BACKING.value());
            rSpec.getDisk().addAll(diskLocator);
        } else {
            rSpec.setDiskMoveType(VirtualMachineRelocateDiskMoveOptions.CREATE_NEW_CHILD_DISK_BACKING.value());
        }
        rSpec.setPool(morResourcePool);

        VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec();
        cloneSpec.setPowerOn(false);
        cloneSpec.setTemplate(false);
        cloneSpec.setLocation(rSpec);
        cloneSpec.setSnapshot(morBaseSnapshot);

        ManagedObjectReference morTask = _context.getService().cloneVMTask(_mor, morFolder, cloneName, cloneSpec);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware cloneVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        return false;
    }

    public VirtualMachineRuntimeInfo getRuntimeInfo() throws Exception {
        return (VirtualMachineRuntimeInfo) _context.getVimClient().getDynamicProperty(_mor, "runtime");
    }

    public VirtualMachineConfigInfo getConfigInfo() throws Exception {
        return (VirtualMachineConfigInfo) _context.getVimClient().getDynamicProperty(_mor, "config");
    }

    public VirtualMachineConfigSummary getConfigSummary() throws Exception {
        return (VirtualMachineConfigSummary) _context.getVimClient().getDynamicProperty(_mor, "summary.config");
    }

    public VirtualMachineFileInfo getFileInfo() throws Exception {
        return (VirtualMachineFileInfo) _context.getVimClient().getDynamicProperty(_mor, "config.files");
    }

    @Override
    public ManagedObjectReference getParentMor() throws Exception {
        return (ManagedObjectReference) _context.getVimClient().getDynamicProperty(_mor, "parent");
    }

    public String[] getNetworks() throws Exception {
        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("Network");
        pSpec.getPathSet().add("name");

        TraversalSpec vm2NetworkTraversal = new TraversalSpec();
        vm2NetworkTraversal.setType("VirtualMachine");
        vm2NetworkTraversal.setPath("network");
        vm2NetworkTraversal.setName("vm2NetworkTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(vm2NetworkTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<PropertyFilterSpec>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(),
                pfSpecArr);

        List<String> networks = new ArrayList<String>();
        if (ocs != null && ocs.size() > 0) {
            for (ObjectContent oc : ocs) {
                networks.add(oc.getPropSet().get(0).getVal().toString());
            }
        }
        return networks.toArray(new String[0]);
    }

    public List<NetworkDetails> getNetworksWithDetails() throws Exception {
        List<NetworkDetails> networks = new ArrayList<NetworkDetails>();

        int gcTagKey = getCustomFieldKey("Network", CustomFieldConstants.CLOUD_GC);

        if (gcTagKey == 0) {
            gcTagKey = getCustomFieldKey("DistributedVirtualPortgroup", CustomFieldConstants.CLOUD_GC_DVP);
            s_logger.debug("The custom key for dvPortGroup is : " + gcTagKey);
        }

        PropertySpec pSpec = new PropertySpec();
        pSpec.setType("Network");
        pSpec.getPathSet().add("name");
        pSpec.getPathSet().add("vm");
        pSpec.getPathSet().add(String.format("value[%d]", gcTagKey));

        TraversalSpec vm2NetworkTraversal = new TraversalSpec();
        vm2NetworkTraversal.setType("VirtualMachine");
        vm2NetworkTraversal.setPath("network");
        vm2NetworkTraversal.setName("vm2NetworkTraversal");

        ObjectSpec oSpec = new ObjectSpec();
        oSpec.setObj(_mor);
        oSpec.setSkip(Boolean.TRUE);
        oSpec.getSelectSet().add(vm2NetworkTraversal);

        PropertyFilterSpec pfSpec = new PropertyFilterSpec();
        pfSpec.getPropSet().add(pSpec);
        pfSpec.getObjectSet().add(oSpec);
        List<PropertyFilterSpec> pfSpecArr = new ArrayList<PropertyFilterSpec>();
        pfSpecArr.add(pfSpec);

        List<ObjectContent> ocs = _context.getService().retrieveProperties(_context.getPropertyCollector(),
                pfSpecArr);

        if (ocs != null && ocs.size() > 0) {
            for (ObjectContent oc : ocs) {
                ArrayOfManagedObjectReference morVms = null;
                String gcTagValue = null;
                String name = null;

                for (DynamicProperty prop : oc.getPropSet()) {
                    if (prop.getName().equals("name"))
                        name = prop.getVal().toString();
                    else if (prop.getName().equals("vm"))
                        morVms = (ArrayOfManagedObjectReference) prop.getVal();
                    else if (prop.getName().startsWith("value[")) {
                        CustomFieldStringValue val = (CustomFieldStringValue) prop.getVal();
                        if (val != null)
                            gcTagValue = val.getValue();
                    }
                }

                NetworkDetails details = new NetworkDetails(name, oc.getObj(),
                        (morVms != null
                                ? morVms.getManagedObjectReference().toArray(
                                        new ManagedObjectReference[morVms.getManagedObjectReference().size()])
                                : null),
                        gcTagValue);

                networks.add(details);
            }
            s_logger.debug("Retrieved " + networks.size() + " networks with key : " + gcTagKey);
        }

        return networks;
    }

    /**
     * Retrieve path info to access VM files via vSphere web interface
     * @return [0] vm-name, [1] data-center-name, [2] datastore-name
     * @throws Exception
     */
    public String[] getHttpAccessPathInfo() throws Exception {
        String[] pathInfo = new String[3];

        Pair<DatacenterMO, String> dcInfo = getOwnerDatacenter();

        VirtualMachineFileInfo fileInfo = getFileInfo();
        String vmxFilePath = fileInfo.getVmPathName();
        String vmxPathTokens[] = vmxFilePath.split("\\[|\\]|/");
        assert (vmxPathTokens.length == 4);
        pathInfo[1] = vmxPathTokens[1].trim(); // vSphere vm name
        pathInfo[2] = dcInfo.second(); // vSphere datacenter name
        pathInfo[3] = vmxPathTokens[0].trim(); // vSphere datastore name
        return pathInfo;
    }

    public String getVmxHttpAccessUrl() throws Exception {
        Pair<DatacenterMO, String> dcInfo = getOwnerDatacenter();

        VirtualMachineFileInfo fileInfo = getFileInfo();
        String vmxFilePath = fileInfo.getVmPathName();
        String vmxPathTokens[] = vmxFilePath.split("\\[|\\]|/");

        StringBuffer sb = new StringBuffer("https://" + _context.getServerAddress() + "/folder/");
        sb.append(URLEncoder.encode(vmxPathTokens[2].trim(), "UTF-8"));
        sb.append("/");
        sb.append(URLEncoder.encode(vmxPathTokens[3].trim(), "UTF-8"));
        sb.append("?dcPath=");
        sb.append(URLEncoder.encode(dcInfo.second(), "UTF-8"));
        sb.append("&dsName=");
        sb.append(URLEncoder.encode(vmxPathTokens[1].trim(), "UTF-8"));

        return sb.toString();
    }

    public boolean setVncConfigInfo(boolean enableVnc, String vncPassword, int vncPort, String keyboard)
            throws Exception {
        VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
        OptionValue[] vncOptions = VmwareHelper.composeVncOptions(null, enableVnc, vncPassword, vncPort, keyboard);
        vmConfigSpec.getExtraConfig().addAll(Arrays.asList(vncOptions));
        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, vmConfigSpec);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware reconfigVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        return false;
    }

    public boolean configureVm(VirtualMachineConfigSpec vmConfigSpec) throws Exception {
        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, vmConfigSpec);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware reconfigVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        return false;
    }

    public boolean configureVm(
            Ternary<VirtualDevice, VirtualDeviceConfigSpecOperation, VirtualDeviceConfigSpecFileOperation>[] devices)
            throws Exception {

        assert (devices != null);

        VirtualMachineConfigSpec configSpec = new VirtualMachineConfigSpec();
        VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[devices.length];
        int i = 0;
        for (Ternary<VirtualDevice, VirtualDeviceConfigSpecOperation, VirtualDeviceConfigSpecFileOperation> deviceTernary : devices) {
            VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
            deviceConfigSpec.setDevice(deviceTernary.first());
            deviceConfigSpec.setOperation(deviceTernary.second());
            deviceConfigSpec.setFileOperation(deviceTernary.third());
            deviceConfigSpecArray[i++] = deviceConfigSpec;
        }
        configSpec.getDeviceChange().addAll(Arrays.asList(deviceConfigSpecArray));

        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, configSpec);

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (result) {
            _context.waitForTaskProgressDone(morTask);
            return true;
        } else {
            s_logger.error("VMware reconfigVM_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        return false;
    }

    public Pair<String, Integer> getVncPort(String hostNetworkName) throws Exception {
        HostMO hostMo = getRunningHost();
        VmwareHypervisorHostNetworkSummary summary = hostMo.getHyperHostNetworkSummary(hostNetworkName);

        VirtualMachineConfigInfo configInfo = getConfigInfo();
        List<OptionValue> values = configInfo.getExtraConfig();

        if (values != null) {
            for (OptionValue option : values) {
                if (option.getKey().equals("RemoteDisplay.vnc.port")) {
                    String value = (String) option.getValue();
                    if (value != null) {
                        return new Pair<String, Integer>(summary.getHostIp(), Integer.parseInt(value));
                    }
                }
            }
        }
        return new Pair<String, Integer>(summary.getHostIp(), 0);
    }

    // vmdkDatastorePath: [datastore name] vmdkFilePath
    public void createDisk(String vmdkDatastorePath, int sizeInMb, ManagedObjectReference morDs, int controllerKey)
            throws Exception {
        createDisk(vmdkDatastorePath, VirtualDiskType.THIN, VirtualDiskMode.PERSISTENT, null, sizeInMb, morDs,
                controllerKey);
    }

    // vmdkDatastorePath: [datastore name] vmdkFilePath
    public void createDisk(String vmdkDatastorePath, VirtualDiskType diskType, VirtualDiskMode diskMode,
            String rdmDeviceName, int sizeInMb, ManagedObjectReference morDs, int controllerKey) throws Exception {

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - createDisk(). target MOR: " + _mor.getValue()
                    + ", vmdkDatastorePath: " + vmdkDatastorePath + ", sizeInMb: " + sizeInMb + ", diskType: "
                    + diskType + ", diskMode: " + diskMode + ", rdmDeviceName: " + rdmDeviceName + ", datastore: "
                    + morDs.getValue() + ", controllerKey: " + controllerKey);

        assert (vmdkDatastorePath != null);
        assert (morDs != null);

        int ideControllerKey = getIDEDeviceControllerKey();
        if (controllerKey < 0) {
            controllerKey = ideControllerKey;
        }

        VirtualDisk newDisk = new VirtualDisk();
        if (diskType == VirtualDiskType.THIN || diskType == VirtualDiskType.PREALLOCATED
                || diskType == VirtualDiskType.EAGER_ZEROED_THICK) {

            VirtualDiskFlatVer2BackingInfo backingInfo = new VirtualDiskFlatVer2BackingInfo();
            backingInfo.setDiskMode(VirtualDiskMode.PERSISTENT.value());
            if (diskType == VirtualDiskType.THIN) {
                backingInfo.setThinProvisioned(true);
            } else {
                backingInfo.setThinProvisioned(false);
            }

            if (diskType == VirtualDiskType.EAGER_ZEROED_THICK) {
                backingInfo.setEagerlyScrub(true);
            } else {
                backingInfo.setEagerlyScrub(false);
            }

            backingInfo.setDatastore(morDs);
            backingInfo.setFileName(vmdkDatastorePath);
            newDisk.setBacking(backingInfo);
        } else if (diskType == VirtualDiskType.RDM || diskType == VirtualDiskType.RDMP) {
            VirtualDiskRawDiskMappingVer1BackingInfo backingInfo = new VirtualDiskRawDiskMappingVer1BackingInfo();
            if (diskType == VirtualDiskType.RDM) {
                backingInfo.setCompatibilityMode("virtualMode");
            } else {
                backingInfo.setCompatibilityMode("physicalMode");
            }
            backingInfo.setDeviceName(rdmDeviceName);
            if (diskType == VirtualDiskType.RDM) {
                backingInfo.setDiskMode(VirtualDiskMode.PERSISTENT.value());
            }

            backingInfo.setDatastore(morDs);
            backingInfo.setFileName(vmdkDatastorePath);
            newDisk.setBacking(backingInfo);
        }

        int deviceNumber = getNextDeviceNumber(controllerKey);
        if (controllerKey != ideControllerKey && VmwareHelper.isReservedScsiDeviceNumber(deviceNumber))
            deviceNumber++;

        newDisk.setControllerKey(controllerKey);
        newDisk.setKey(-deviceNumber);
        newDisk.setUnitNumber(deviceNumber);
        newDisk.setCapacityInKB(sizeInMb * 1024);

        VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
        VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

        deviceConfigSpec.setDevice(newDisk);
        deviceConfigSpec.setFileOperation(VirtualDeviceConfigSpecFileOperation.CREATE);
        deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);

        reConfigSpec.getDeviceChange().add(deviceConfigSpec);

        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
        boolean result = _context.getVimClient().waitForTask(morTask);

        if (!result) {
            if (s_logger.isTraceEnabled())
                s_logger.trace("vCenter API trace - createDisk() done(failed)");
            throw new Exception("Unable to create disk " + vmdkDatastorePath + " due to "
                    + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        _context.waitForTaskProgressDone(morTask);

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - createDisk() done(successfully)");
    }

    public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs) throws Exception {

        if (s_logger.isTraceEnabled())
            s_logger.trace(
                    "vCenter API trace - attachDisk(). target MOR: " + _mor.getValue() + ", vmdkDatastorePath: "
                            + new Gson().toJson(vmdkDatastorePathChain) + ", datastore: " + morDs.getValue());

        synchronized (_mor.getValue().intern()) {
            VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, null, getScsiDeviceControllerKey(),
                    vmdkDatastorePathChain, morDs, -1, 1);
            VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
            VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

            deviceConfigSpec.setDevice(newDisk);
            deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);

            reConfigSpec.getDeviceChange().add(deviceConfigSpec);

            ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
            boolean result = _context.getVimClient().waitForTask(morTask);

            if (!result) {
                if (s_logger.isTraceEnabled())
                    s_logger.trace("vCenter API trace - attachDisk() done(failed)");
                throw new Exception("Failed to attach disk due to " + TaskMO.getTaskFailureInfo(_context, morTask));
            }

            _context.waitForTaskProgressDone(morTask);
        }

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - attachDisk() done(successfully)");
    }

    public void attachDisk(Pair<String, ManagedObjectReference>[] vmdkDatastorePathChain, int controllerKey)
            throws Exception {

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - attachDisk(). target MOR: " + _mor.getValue()
                    + ", vmdkDatastorePath: " + new Gson().toJson(vmdkDatastorePathChain));

        synchronized (_mor.getValue().intern()) {
            VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, controllerKey, vmdkDatastorePathChain, -1,
                    1);
            VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
            VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

            deviceConfigSpec.setDevice(newDisk);
            deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);

            reConfigSpec.getDeviceChange().add(deviceConfigSpec);

            ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
            boolean result = _context.getVimClient().waitForTask(morTask);

            if (!result) {
                if (s_logger.isTraceEnabled())
                    s_logger.trace("vCenter API trace - attachDisk() done(failed)");
                throw new Exception("Failed to attach disk due to " + TaskMO.getTaskFailureInfo(_context, morTask));
            }

            _context.waitForTaskProgressDone(morTask);
        }

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - attachDisk() done(successfully)");
    }

    // vmdkDatastorePath: [datastore name] vmdkFilePath
    public List<Pair<String, ManagedObjectReference>> detachDisk(String vmdkDatastorePath,
            boolean deleteBackingFile) throws Exception {

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - detachDisk(). target MOR: " + _mor.getValue()
                    + ", vmdkDatastorePath: " + vmdkDatastorePath + ", deleteBacking: " + deleteBackingFile);

        // Note: if VM has been taken snapshot, original backing file will be renamed, therefore, when we try to find the matching
        // VirtualDisk, we only perform prefix matching
        Pair<VirtualDisk, String> deviceInfo = getDiskDevice(vmdkDatastorePath, false);
        if (deviceInfo == null) {
            s_logger.warn("vCenter API trace - detachDisk() done (failed)");
            throw new Exception("No such disk device: " + vmdkDatastorePath);
        }

        List<Pair<String, ManagedObjectReference>> chain = getDiskDatastorePathChain(deviceInfo.first(), true);

        VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
        VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

        deviceConfigSpec.setDevice(deviceInfo.first());
        if (deleteBackingFile) {
            deviceConfigSpec.setFileOperation(VirtualDeviceConfigSpecFileOperation.DESTROY);
        }
        deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.REMOVE);

        reConfigSpec.getDeviceChange().add(deviceConfigSpec);

        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
        boolean result = _context.getVimClient().waitForTask(morTask);

        if (!result) {
            if (s_logger.isTraceEnabled())
                s_logger.trace("vCenter API trace - detachDisk() done (failed)");

            throw new Exception("Failed to detach disk due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }
        _context.waitForTaskProgressDone(morTask);

        // VMware does not update snapshot references to the detached disk, we have to work around it
        SnapshotDescriptor snapshotDescriptor = null;
        try {
            snapshotDescriptor = getSnapshotDescriptor();
        } catch (Exception e) {
            s_logger.info("Unable to retrieve snapshot descriptor, will skip updating snapshot reference");
        }

        if (snapshotDescriptor != null) {
            for (Pair<String, ManagedObjectReference> pair : chain) {
                DatastoreFile dsFile = new DatastoreFile(pair.first());
                snapshotDescriptor.removeDiskReferenceFromSnapshot(dsFile.getFileName());
            }

            Pair<DatacenterMO, String> dcPair = getOwnerDatacenter();
            String dsPath = getSnapshotDescriptorDatastorePath();
            assert (dsPath != null);
            String url = getContext().composeDatastoreBrowseUrl(dcPair.second(), dsPath);
            getContext().uploadResourceContent(url, snapshotDescriptor.getVmsdContent());
        }

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - detachDisk() done (successfully)");
        return chain;
    }

    public void detachAllDisks() throws Exception {
        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - detachAllDisk(). target MOR: " + _mor.getValue());

        VirtualDisk[] disks = getAllDiskDevice();
        if (disks.length > 0) {
            VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
            VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[disks.length];

            for (int i = 0; i < disks.length; i++) {
                deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();
                deviceConfigSpecArray[i].setDevice(disks[i]);
                deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.REMOVE);
            }
            reConfigSpec.getDeviceChange().addAll(Arrays.asList(deviceConfigSpecArray));

            ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
            boolean result = _context.getVimClient().waitForTask(morTask);

            if (!result) {
                if (s_logger.isTraceEnabled())
                    s_logger.trace("vCenter API trace - detachAllDisk() done(failed)");
                throw new Exception("Failed to detach disk due to " + TaskMO.getTaskFailureInfo(_context, morTask));
            }

            _context.waitForTaskProgressDone(morTask);
        }

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - detachAllDisk() done(successfully)");
    }

    // isoDatastorePath: [datastore name] isoFilePath
    public void attachIso(String isoDatastorePath, ManagedObjectReference morDs, boolean connect,
            boolean connectAtBoot) throws Exception {

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - attachIso(). target MOR: " + _mor.getValue()
                    + ", isoDatastorePath: " + isoDatastorePath + ", datastore: " + morDs.getValue() + ", connect: "
                    + connect + ", connectAtBoot: " + connectAtBoot);

        assert (isoDatastorePath != null);
        assert (morDs != null);

        boolean newCdRom = false;
        VirtualCdrom cdRom = (VirtualCdrom) getIsoDevice();
        if (cdRom == null) {
            newCdRom = true;
            cdRom = new VirtualCdrom();
            cdRom.setControllerKey(getIDEDeviceControllerKey());

            int deviceNumber = getNextIDEDeviceNumber();
            cdRom.setUnitNumber(deviceNumber);
            cdRom.setKey(-deviceNumber);
        }

        VirtualDeviceConnectInfo cInfo = new VirtualDeviceConnectInfo();
        cInfo.setConnected(connect);
        cInfo.setStartConnected(connectAtBoot);
        cdRom.setConnectable(cInfo);

        VirtualCdromIsoBackingInfo backingInfo = new VirtualCdromIsoBackingInfo();
        backingInfo.setFileName(isoDatastorePath);
        backingInfo.setDatastore(morDs);
        cdRom.setBacking(backingInfo);

        VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
        //VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[1];
        VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

        deviceConfigSpec.setDevice(cdRom);
        if (newCdRom) {
            deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
        } else {
            deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
        }

        //deviceConfigSpecArray[0] = deviceConfigSpec;
        reConfigSpec.getDeviceChange().add(deviceConfigSpec);

        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
        boolean result = _context.getVimClient().waitForTask(morTask);

        if (!result) {
            if (s_logger.isTraceEnabled())
                s_logger.trace("vCenter API trace - detachIso() done(failed)");
            throw new Exception("Failed to attach ISO due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        }

        _context.waitForTaskProgressDone(morTask);

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - detachIso() done(successfully)");
    }

    public void detachIso(String isoDatastorePath) throws Exception {
        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - detachIso(). target MOR: " + _mor.getValue()
                    + ", isoDatastorePath: " + isoDatastorePath);

        VirtualDevice device = getIsoDevice();
        if (device == null) {
            if (s_logger.isTraceEnabled())
                s_logger.trace("vCenter API trace - detachIso() done(failed)");
            throw new Exception("Unable to find a CDROM device");
        }

        VirtualCdromRemotePassthroughBackingInfo backingInfo = new VirtualCdromRemotePassthroughBackingInfo();
        backingInfo.setDeviceName("");
        device.setBacking(backingInfo);

        VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
        //VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[1];
        VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

        deviceConfigSpec.setDevice(device);
        deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.EDIT);

        //deviceConfigSpecArray[0] = deviceConfigSpec;
        reConfigSpec.getDeviceChange().add(deviceConfigSpec);

        ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);

        // Monitor VM questions
        final Boolean[] flags = { false };
        final VirtualMachineMO vmMo = this;
        Future<?> future = MonitorServiceExecutor.submit(new Runnable() {
            @Override
            public void run() {
                s_logger.info("VM Question monitor started...");

                while (!flags[0]) {
                    try {
                        VirtualMachineRuntimeInfo runtimeInfo = vmMo.getRuntimeInfo();
                        VirtualMachineQuestionInfo question = runtimeInfo.getQuestion();
                        if (question != null) {
                            if (s_logger.isTraceEnabled()) {
                                s_logger.trace("Question id: " + question.getId());
                                s_logger.trace("Question text: " + question.getText());
                            }
                            if (question.getMessage() != null) {
                                for (VirtualMachineMessage msg : question.getMessage()) {
                                    if (s_logger.isTraceEnabled()) {
                                        s_logger.trace("msg id: " + msg.getId());
                                        s_logger.trace("msg text: " + msg.getText());
                                    }
                                    if ("msg.cdromdisconnect.locked".equalsIgnoreCase(msg.getId())) {
                                        s_logger.info(
                                                "Found that VM has a pending question that we need to answer programmatically, question id: "
                                                        + msg.getId()
                                                        + ", for safe operation we will automatically decline it");
                                        vmMo.answerVM(question.getId(), "1");
                                        break;
                                    }
                                }
                            } else if (question.getText() != null) {
                                String text = question.getText();
                                String msgId;
                                String msgText;
                                if (s_logger.isDebugEnabled()) {
                                    s_logger.debug("question text : " + text);
                                }
                                String[] tokens = text.split(":");
                                msgId = tokens[0];
                                msgText = tokens[1];
                                if ("msg.cdromdisconnect.locked".equalsIgnoreCase(msgId)) {
                                    s_logger.info(
                                            "Found that VM has a pending question that we need to answer programmatically, question id: "
                                                    + question.getId() + ". Message id : " + msgId
                                                    + ". Message text : " + msgText
                                                    + ", for safe operation we will automatically decline it.");
                                    vmMo.answerVM(question.getId(), "1");
                                }
                            }

                            ChoiceOption choice = question.getChoice();
                            if (choice != null) {
                                for (ElementDescription info : choice.getChoiceInfo()) {
                                    if (s_logger.isTraceEnabled()) {
                                        s_logger.trace("Choice option key: " + info.getKey());
                                        s_logger.trace("Choice option label: " + info.getLabel());
                                    }
                                }
                            }
                        }
                    } catch (Throwable e) {
                        s_logger.error("Unexpected exception: ", e);
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                s_logger.info("VM Question monitor stopped");
            }
        });
        try {
            boolean result = _context.getVimClient().waitForTask(morTask);
            if (!result) {
                if (s_logger.isDebugEnabled())
                    s_logger.trace("vCenter API trace - detachIso() done(failed)");
                throw new Exception("Failed to detachIso due to " + TaskMO.getTaskFailureInfo(_context, morTask));
            }
            _context.waitForTaskProgressDone(morTask);
            s_logger.trace("vCenter API trace - detachIso() done(successfully)");
        } finally {
            flags[0] = true;
            future.cancel(true);
        }
    }

    public Pair<VmdkFileDescriptor, byte[]> getVmdkFileInfo(String vmdkDatastorePath) throws Exception {

        if (s_logger.isTraceEnabled())
            s_logger.trace("vCenter API trace - getVmdkFileInfo(). target MOR: " + _mor.getValue()
                    + ", vmdkDatastorePath: " + vmdkDatastorePath);

        Pair<DatacenterMO, String> dcPair = getOwnerDatacenter();

        String url = getContext().composeDatastoreBrowseUrl(dcPair.second(), vmdkDatastorePath);
        byte[] content = getContext().getResourceContent(url);
        VmdkFileDescriptor descriptor = new VmdkFileDescriptor();
        descriptor.parse(content);

        Pair<VmdkFileDescriptor, byte[]> result = new Pair<VmdkFileDescriptor, byte[]>(descriptor, content);
        if (s_logger.isTraceEnabled()) {
            s_logger.trace("vCenter API trace - getVmdkFileInfo() done");
            s_logger.trace("VMDK file descriptor: " + new Gson().toJson(result.first()));
        }
        return result;
    }

    public void exportVm(String exportDir, String exportName, boolean packToOva, boolean leaveOvaFileOnly)
            throws Exception {
        ManagedObjectReference morOvf = _context.getServiceContent().getOvfManager();

        VirtualMachineRuntimeInfo runtimeInfo = getRuntimeInfo();
        HostMO hostMo = new HostMO(_context, runtimeInfo.getHost());
        String hostName = hostMo.getHostName();
        String vmName = getVmName();

        DatacenterMO dcMo = new DatacenterMO(_context, hostMo.getHyperHostDatacenter());

        if (runtimeInfo.getPowerState() != VirtualMachinePowerState.POWERED_OFF) {
            String msg = "Unable to export VM because it is not at powerdOff state. vmName: " + vmName + ", host: "
                    + hostName;
            s_logger.error(msg);
            throw new Exception(msg);
        }

        ManagedObjectReference morLease = _context.getService().exportVm(getMor());
        if (morLease == null) {
            s_logger.error("exportVm() failed");
            throw new Exception("exportVm() failed");
        }

        HttpNfcLeaseMO leaseMo = new HttpNfcLeaseMO(_context, morLease);
        HttpNfcLeaseState state = leaseMo
                .waitState(new HttpNfcLeaseState[] { HttpNfcLeaseState.READY, HttpNfcLeaseState.ERROR });

        try {
            if (state == HttpNfcLeaseState.READY) {
                final HttpNfcLeaseMO.ProgressReporter progressReporter = leaseMo.createProgressReporter();

                boolean success = false;
                List<String> fileNames = new ArrayList<String>();
                try {
                    HttpNfcLeaseInfo leaseInfo = leaseMo.getLeaseInfo();
                    final long totalBytes = leaseInfo.getTotalDiskCapacityInKB() * 1024;
                    long totalBytesDownloaded = 0;

                    List<HttpNfcLeaseDeviceUrl> deviceUrls = leaseInfo.getDeviceUrl();
                    s_logger.info("volss: copy vmdk and ovf file starts " + System.currentTimeMillis());
                    if (deviceUrls != null) {
                        OvfFile[] ovfFiles = new OvfFile[deviceUrls.size()];
                        for (int i = 0; i < deviceUrls.size(); i++) {
                            String deviceId = deviceUrls.get(i).getKey();
                            String deviceUrlStr = deviceUrls.get(i).getUrl();
                            String orgDiskFileName = deviceUrlStr.substring(deviceUrlStr.lastIndexOf("/") + 1);
                            String diskFileName = String.format("%s-disk%d%s", exportName, i,
                                    VmwareHelper.getFileExtension(orgDiskFileName, ".vmdk"));
                            String diskUrlStr = deviceUrlStr.replace("*", hostName);
                            diskUrlStr = HypervisorHostHelper.resolveHostNameInUrl(dcMo, diskUrlStr);
                            String diskLocalPath = exportDir + File.separator + diskFileName;
                            fileNames.add(diskLocalPath);

                            if (s_logger.isInfoEnabled()) {
                                s_logger.info("Download VMDK file for export. url: " + deviceUrlStr);
                            }
                            long lengthOfDiskFile = _context.downloadVmdkFile(diskUrlStr, diskLocalPath,
                                    totalBytesDownloaded, new ActionDelegate<Long>() {
                                        @Override
                                        public void action(Long param) {
                                            if (s_logger.isTraceEnabled()) {
                                                s_logger.trace("Download progress " + param + "/" + totalBytes);
                                            }
                                            progressReporter.reportProgress((int) (param * 100 / totalBytes));
                                        }
                                    });
                            totalBytesDownloaded += lengthOfDiskFile;

                            OvfFile ovfFile = new OvfFile();
                            ovfFile.setPath(diskFileName);
                            ovfFile.setDeviceId(deviceId);
                            ovfFile.setSize(lengthOfDiskFile);
                            ovfFiles[i] = ovfFile;
                        }

                        // write OVF descriptor file
                        OvfCreateDescriptorParams ovfDescParams = new OvfCreateDescriptorParams();
                        ovfDescParams.getOvfFiles().addAll(Arrays.asList(ovfFiles));
                        OvfCreateDescriptorResult ovfCreateDescriptorResult = _context.getService()
                                .createDescriptor(morOvf, getMor(), ovfDescParams);
                        String ovfPath = exportDir + File.separator + exportName + ".ovf";
                        fileNames.add(ovfPath);

                        FileWriter out = new FileWriter(ovfPath);
                        out.write(ovfCreateDescriptorResult.getOvfDescriptor());
                        out.close();

                        // tar files into OVA
                        if (packToOva) {
                            // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature)
                            s_logger.info("Sync file system before we package OVA...");

                            Script commandSync = new Script(true, "sync", 0, s_logger);
                            commandSync.execute();

                            Script command = new Script(false, "tar", 0, s_logger);
                            command.setWorkDir(exportDir);
                            command.add("-cf", exportName + ".ova");
                            command.add(exportName + ".ovf"); // OVF file should be the first file in OVA archive
                            for (String name : fileNames) {
                                command.add((new File(name).getName()));
                            }

                            s_logger.info("Package OVA with commmand: " + command.toString());
                            command.execute();

                            // to be safe, physically test existence of the target OVA file
                            if ((new File(exportDir + File.separator + exportName + ".ova")).exists()) {
                                success = true;
                            } else {
                                s_logger.error(exportDir + File.separator + exportName
                                        + ".ova is not created as expected");
                            }
                        } else {
                            success = true;
                        }
                    }
                    s_logger.info("volss: copy vmdk and ovf file finishes " + System.currentTimeMillis());
                } catch (Throwable e) {
                    s_logger.error("Unexpected exception ", e);
                } finally {
                    progressReporter.close();

                    if (leaveOvaFileOnly) {
                        for (String name : fileNames) {
                            new File(name).delete();
                        }
                    }

                    if (!success)
                        throw new Exception("Unable to finish the whole process to package as a OVA file");
                }
            }
        } finally {
            leaseMo.updateLeaseProgress(100);
            leaseMo.completeLease();
        }
    }

    // snapshot directory in format of: /vmfs/volumes/<datastore name>/<path>
    @Deprecated
    public void setSnapshotDirectory(String snapshotDir) throws Exception {
        VirtualMachineFileInfo fileInfo = getFileInfo();
        Pair<DatacenterMO, String> dcInfo = getOwnerDatacenter();
        String vmxUrl = _context.composeDatastoreBrowseUrl(dcInfo.second(), fileInfo.getVmPathName());
        byte[] vmxContent = _context.getResourceContent(vmxUrl);

        BufferedReader in = null;
        BufferedWriter out = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        boolean replaced = false;
        try {
            in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(vmxContent)));
            out = new BufferedWriter(new OutputStreamWriter(bos));
            String line;
            while ((line = in.readLine()) != null) {
                if (line.startsWith("workingDir")) {
                    replaced = true;
                    out.write(String.format("workingDir=\"%s\"", snapshotDir));
                    out.newLine();
                } else {
                    out.write(line);
                    out.newLine();
                }
            }

            if (!replaced) {
                out.newLine();
                out.write(String.format("workingDir=\"%s\"", snapshotDir));
                out.newLine();
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
        _context.uploadResourceContent(vmxUrl, bos.toByteArray());

        // It seems that I don't need to do re-registration. VMware has bug in writing the correct snapshot's VMDK path to
        // its disk backing info anyway.
        // redoRegistration();
    }

    // destName does not contain extension name
    public void backupCurrentSnapshot(String deviceName, ManagedObjectReference morDestDs, String destDsDirectory,
            String destName, boolean includeBase) throws Exception {

        SnapshotDescriptor descriptor = getSnapshotDescriptor();
        SnapshotInfo[] snapshotInfo = descriptor.getCurrentDiskChain();
        if (snapshotInfo.length == 0) {
            String msg = "No snapshot found in this VM";
            throw new Exception(msg);
        }

        HostMO hostMo = getRunningHost();
        DatacenterMO dcMo = getOwnerDatacenter().first();
        List<Pair<ManagedObjectReference, String>> mounts = hostMo.getDatastoreMountsOnHost();
        VirtualMachineFileInfo vmFileInfo = getFileInfo();

        List<Ternary<String, String, String>> backupInfo = new ArrayList<Ternary<String, String, String>>();

        for (int i = 0; i < snapshotInfo.length; i++) {
            if (!includeBase && i == snapshotInfo.length - 1) {
                break;
            }

            SnapshotDescriptor.DiskInfo[] disks = snapshotInfo[i].getDisks();
            if (disks != null) {
                String destBaseFileName;
                String destFileName;
                String destParentFileName;
                for (SnapshotDescriptor.DiskInfo disk : disks) {
                    if (deviceName == null || deviceName.equals(disk.getDeviceName())) {
                        String srcVmdkFullDsPath = getSnapshotDiskFileDatastorePath(vmFileInfo, mounts,
                                disk.getDiskFileName());
                        Pair<DatastoreMO, String> srcDsInfo = getOwnerDatastore(srcVmdkFullDsPath);

                        Pair<VmdkFileDescriptor, byte[]> vmdkInfo = getVmdkFileInfo(srcVmdkFullDsPath);
                        String srcVmdkBaseFilePath = DatastoreFile.getCompanionDatastorePath(srcVmdkFullDsPath,
                                vmdkInfo.first().getBaseFileName());

                        destFileName = destName + (snapshotInfo.length - i - 1) + ".vmdk";
                        if (vmdkInfo.first().getParentFileName() != null) {
                            destBaseFileName = destName + (snapshotInfo.length - i - 1) + "-delta.vmdk";
                            destParentFileName = destName + (snapshotInfo.length - i - 2) + ".vmdk";
                        } else {
                            destBaseFileName = destName + (snapshotInfo.length - i - 1) + "-flat.vmdk";
                            destParentFileName = null;
                        }

                        s_logger.info("Copy VMDK base file " + srcVmdkBaseFilePath + " to " + destDsDirectory + "/"
                                + destBaseFileName);
                        srcDsInfo.first().copyDatastoreFile(srcVmdkBaseFilePath, dcMo.getMor(), morDestDs,
                                destDsDirectory + "/" + destBaseFileName, dcMo.getMor(), true);

                        byte[] newVmdkContent = VmdkFileDescriptor.changeVmdkContentBaseInfo(vmdkInfo.second(),
                                destBaseFileName, destParentFileName);
                        String vmdkUploadUrl = getContext().composeDatastoreBrowseUrl(dcMo.getName(),
                                destDsDirectory + "/" + destFileName);

                        s_logger.info("Upload VMDK content file to " + destDsDirectory + "/" + destFileName);
                        getContext().uploadResourceContent(vmdkUploadUrl, newVmdkContent);

                        backupInfo.add(new Ternary<String, String, String>(destFileName, destBaseFileName,
                                destParentFileName));
                    }
                }
            }
        }

        byte[] vdiskInfo = VmwareHelper.composeDiskInfo(backupInfo, snapshotInfo.length, includeBase);
        String vdiskUploadUrl = getContext().composeDatastoreBrowseUrl(dcMo.getName(),
                destDsDirectory + "/" + destName + ".vdisk");
        getContext().uploadResourceContent(vdiskUploadUrl, vdiskInfo);
    }

    public String[] getCurrentSnapshotDiskChainDatastorePaths(String diskDevice) throws Exception {
        HostMO hostMo = getRunningHost();
        List<Pair<ManagedObjectReference, String>> mounts = hostMo.getDatastoreMountsOnHost();
        VirtualMachineFileInfo vmFileInfo = getFileInfo();

        SnapshotDescriptor descriptor = getSnapshotDescriptor();
        SnapshotInfo[] snapshotInfo = descriptor.getCurrentDiskChain();

        List<String> diskDsFullPaths = new ArrayList<String>();
        for (int i = 0; i < snapshotInfo.length; i++) {
            SnapshotDescriptor.DiskInfo[] disks = snapshotInfo[i].getDisks();
            if (disks != null) {
                for (SnapshotDescriptor.DiskInfo disk : disks) {
                    String deviceNameInDisk = disk.getDeviceName();
                    if (diskDevice == null || diskDevice.equalsIgnoreCase(deviceNameInDisk)) {
                        String vmdkFullDsPath = getSnapshotDiskFileDatastorePath(vmFileInfo, mounts,
                                disk.getDiskFileName());
                        diskDsFullPaths.add(vmdkFullDsPath);
                    }
                }
            }
        }
        return diskDsFullPaths.toArray(new String[0]);
    }

    // return the disk chain (VMDK datastore paths) for cloned snapshot
    public Pair<VirtualMachineMO, String[]> cloneFromCurrentSnapshot(String clonedVmName, int cpuSpeedMHz,
            int memoryMb, String diskDevice, ManagedObjectReference morDs) throws Exception {
        assert (morDs != null);
        String[] disks = getCurrentSnapshotDiskChainDatastorePaths(diskDevice);
        VirtualMachineMO clonedVm = cloneFromDiskChain(clonedVmName, cpuSpeedMHz, memoryMb, disks, morDs);
        return new Pair<VirtualMachineMO, String[]>(clonedVm, disks);
    }

    public VirtualMachineMO cloneFromDiskChain(String clonedVmName, int cpuSpeedMHz, int memoryMb, String[] disks,
            ManagedObjectReference morDs) throws Exception {
        assert (disks != null);
        assert (disks.length >= 1);

        HostMO hostMo = getRunningHost();

        VirtualMachineMO clonedVmMo = HypervisorHostHelper.createWorkerVM(hostMo,
                new DatastoreMO(hostMo.getContext(), morDs), clonedVmName);
        if (clonedVmMo == null)
            throw new Exception("Unable to find just-created blank VM");

        boolean bSuccess = false;
        try {
            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
            VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

            VirtualDevice device = VmwareHelper.prepareDiskDevice(clonedVmMo, null, -1, disks, morDs, -1, 1);

            deviceConfigSpec.setDevice(device);
            deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
            vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
            clonedVmMo.configureVm(vmConfigSpec);
            bSuccess = true;
            return clonedVmMo;
        } finally {
            if (!bSuccess) {
                clonedVmMo.detachAllDisks();
                clonedVmMo.destroy();
            }
        }
    }

    public GuestOsDescriptor getGuestOsDescriptor(String guestOsId) throws Exception {
        GuestOsDescriptor guestOsDescriptor = null;
        ManagedObjectReference vmEnvironmentBrowser = _context.getVimClient().getMoRefProp(_mor,
                "environmentBrowser");
        VirtualMachineConfigOption vmConfigOption = _context.getService().queryConfigOption(vmEnvironmentBrowser,
                null, null);
        List<GuestOsDescriptor> guestDescriptors = vmConfigOption.getGuestOSDescriptor();
        for (GuestOsDescriptor descriptor : guestDescriptors) {
            if (guestOsId != null && guestOsId.equalsIgnoreCase(descriptor.getId())) {
                guestOsDescriptor = descriptor;
                break;
            }
        }
        return guestOsDescriptor;
    }

    public void plugDevice(VirtualDevice device) throws Exception {
        VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
        VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
        deviceConfigSpec.setDevice(device);
        deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);

        vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
        if (!configureVm(vmConfigSpec)) {
            throw new Exception("Failed to add devices");
        }
    }

    public void tearDownDevice(VirtualDevice device) throws Exception {
        VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
        VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
        deviceConfigSpec.setDevice(device);
        deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.REMOVE);

        vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
        if (!configureVm(vmConfigSpec)) {
            throw new Exception("Failed to detach devices");
        }
    }

    public void tearDownDevices(Class<?>[] deviceClasses) throws Exception {
        VirtualDevice[] devices = getMatchedDevices(deviceClasses);

        if (devices.length > 0) {
            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
            VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[devices.length];

            for (int i = 0; i < devices.length; i++) {
                deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();
                deviceConfigSpecArray[i].setDevice(devices[i]);
                deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.REMOVE);
                vmConfigSpec.getDeviceChange().add(deviceConfigSpecArray[i]);
            }

            if (!configureVm(vmConfigSpec)) {
                throw new Exception("Failed to detach devices");
            }
        }
    }

    public void copyAllVmDiskFiles(DatastoreMO destDsMo, String destDsDir, boolean followDiskChain)
            throws Exception {
        VirtualDevice[] disks = getAllDiskDevice();
        DatacenterMO dcMo = getOwnerDatacenter().first();
        if (disks != null) {
            for (VirtualDevice disk : disks) {
                List<Pair<String, ManagedObjectReference>> vmdkFiles = getDiskDatastorePathChain((VirtualDisk) disk,
                        followDiskChain);
                for (Pair<String, ManagedObjectReference> fileItem : vmdkFiles) {
                    DatastoreMO srcDsMo = new DatastoreMO(_context, fileItem.second());

                    DatastoreFile srcFile = new DatastoreFile(fileItem.first());
                    DatastoreFile destFile = new DatastoreFile(destDsMo.getName(), destDsDir,
                            srcFile.getFileName());

                    Pair<VmdkFileDescriptor, byte[]> vmdkDescriptor = null;

                    vmdkDescriptor = getVmdkFileInfo(fileItem.first());

                    s_logger.info("Copy VM disk file " + srcFile.getPath() + " to " + destFile.getPath());
                    srcDsMo.copyDatastoreFile(fileItem.first(), dcMo.getMor(), destDsMo.getMor(),
                            destFile.getPath(), dcMo.getMor(), true);

                    if (vmdkDescriptor != null) {
                        String vmdkBaseFileName = vmdkDescriptor.first().getBaseFileName();
                        String baseFilePath = srcFile.getCompanionPath(vmdkBaseFileName);
                        destFile = new DatastoreFile(destDsMo.getName(), destDsDir, vmdkBaseFileName);

                        s_logger.info("Copy VM disk file " + baseFilePath + " to " + destFile.getPath());
                        srcDsMo.copyDatastoreFile(baseFilePath, dcMo.getMor(), destDsMo.getMor(),
                                destFile.getPath(), dcMo.getMor(), true);
                    }
                }
            }
        }
    }

    public List<String> getVmdkFileBaseNames() throws Exception {
        List<String> vmdkFileBaseNames = new ArrayList<String>();
        VirtualDevice[] devices = getAllDiskDevice();
        for (VirtualDevice device : devices) {
            if (device instanceof VirtualDisk) {
                vmdkFileBaseNames.add(getVmdkFileBaseName((VirtualDisk) device));
            }
        }
        return vmdkFileBaseNames;
    }

    public String getVmdkFileBaseName(VirtualDisk disk) throws Exception {
        String vmdkFileBaseName = null;
        VirtualDeviceBackingInfo backingInfo = disk.getBacking();
        if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
            VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;
            DatastoreFile dsBackingFile = new DatastoreFile(diskBackingInfo.getFileName());
            vmdkFileBaseName = dsBackingFile.getFileBaseName();
        }
        return vmdkFileBaseName;
    }

    // this method relies on un-offical VMware API
    @Deprecated
    public void moveAllVmDiskFiles(DatastoreMO destDsMo, String destDsDir, boolean followDiskChain)
            throws Exception {
        VirtualDevice[] disks = getAllDiskDevice();
        DatacenterMO dcMo = getOwnerDatacenter().first();
        if (disks != null) {
            for (VirtualDevice disk : disks) {
                List<Pair<String, ManagedObjectReference>> vmdkFiles = getDiskDatastorePathChain((VirtualDisk) disk,
                        followDiskChain);
                for (Pair<String, ManagedObjectReference> fileItem : vmdkFiles) {
                    DatastoreMO srcDsMo = new DatastoreMO(_context, fileItem.second());

                    DatastoreFile srcFile = new DatastoreFile(fileItem.first());
                    DatastoreFile destFile = new DatastoreFile(destDsMo.getName(), destDsDir,
                            srcFile.getFileName());

                    Pair<VmdkFileDescriptor, byte[]> vmdkDescriptor = null;
                    vmdkDescriptor = getVmdkFileInfo(fileItem.first());

                    s_logger.info("Move VM disk file " + srcFile.getPath() + " to " + destFile.getPath());
                    srcDsMo.moveDatastoreFile(fileItem.first(), dcMo.getMor(), destDsMo.getMor(),
                            destFile.getPath(), dcMo.getMor(), true);

                    if (vmdkDescriptor != null) {
                        String vmdkBaseFileName = vmdkDescriptor.first().getBaseFileName();
                        String baseFilePath = srcFile.getCompanionPath(vmdkBaseFileName);
                        destFile = new DatastoreFile(destDsMo.getName(), destDsDir, vmdkBaseFileName);

                        s_logger.info("Move VM disk file " + baseFilePath + " to " + destFile.getPath());
                        srcDsMo.moveDatastoreFile(baseFilePath, dcMo.getMor(), destDsMo.getMor(),
                                destFile.getPath(), dcMo.getMor(), true);
                    }
                }
            }
        }
    }

    public int getNextScsiDiskDeviceNumber() throws Exception {
        int scsiControllerKey = getScsiDeviceControllerKey();
        int deviceNumber = getNextDeviceNumber(scsiControllerKey);
        if (VmwareHelper.isReservedScsiDeviceNumber(deviceNumber))
            deviceNumber++;

        return deviceNumber;
    }

    public int getScsiDeviceControllerKey() throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualLsiLogicController) {
                    return device.getKey();
                }
            }
        }

        assert (false);
        throw new Exception("SCSI Controller Not Found");
    }

    public int getScsiDeviceControllerKeyNoException() throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualLsiLogicController) {
                    return device.getKey();
                }
            }
        }

        return -1;
    }

    public void ensureScsiDeviceController() throws Exception {
        int scsiControllerKey = getScsiDeviceControllerKeyNoException();
        if (scsiControllerKey < 0) {
            VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec();

            // Scsi controller
            VirtualLsiLogicController scsiController = new VirtualLsiLogicController();
            scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING);
            scsiController.setBusNumber(0);
            scsiController.setKey(1);
            VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec();
            scsiControllerSpec.setDevice(scsiController);
            scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);

            vmConfig.getDeviceChange().add(scsiControllerSpec);
            if (configureVm(vmConfig)) {
                throw new Exception("Unable to add Scsi controller");
            }
        }
    }

    private static String trimSnapshotDeltaPostfix(String name) {
        String[] tokens = name.split("-");
        if (tokens.length > 1 && tokens[tokens.length - 1].matches("[0-9]{6,}")) {
            List<String> trimmedTokens = new ArrayList<String>();
            for (int i = 0; i < tokens.length - 1; i++)
                trimmedTokens.add(tokens[i]);
            return StringUtils.join(trimmedTokens, "-");
        }
        return name;
    }

    // return pair of VirtualDisk and disk device bus name(ide0:0, etc)
    public Pair<VirtualDisk, String> getDiskDevice(String vmdkDatastorePath, boolean matchExactly)
            throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        DatastoreFile dsSrcFile = new DatastoreFile(vmdkDatastorePath);
        String srcBaseName = dsSrcFile.getFileBaseName();

        srcBaseName = trimSnapshotDeltaPostfix(srcBaseName);

        s_logger.info("Look for disk device info from volume : " + vmdkDatastorePath + " with trimmed base name: "
                + srcBaseName);

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualDisk) {
                    s_logger.info("Test against disk device, controller key: " + device.getControllerKey()
                            + ", unit number: " + device.getUnitNumber());

                    VirtualDeviceBackingInfo backingInfo = ((VirtualDisk) device).getBacking();
                    if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
                        VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;
                        do {
                            s_logger.info("Test against disk backing : " + diskBackingInfo.getFileName());

                            DatastoreFile dsBackingFile = new DatastoreFile(diskBackingInfo.getFileName());
                            String backingBaseName = dsBackingFile.getFileBaseName();
                            if (matchExactly) {
                                if (backingBaseName.equalsIgnoreCase(srcBaseName)) {
                                    String deviceNumbering = getDeviceBusName(devices, device);

                                    s_logger.info("Disk backing : " + diskBackingInfo.getFileName()
                                            + " matches ==> " + deviceNumbering);
                                    return new Pair<VirtualDisk, String>((VirtualDisk) device, deviceNumbering);
                                }
                            } else {
                                if (backingBaseName.contains(srcBaseName)) {
                                    String deviceNumbering = getDeviceBusName(devices, device);

                                    s_logger.info("Disk backing : " + diskBackingInfo.getFileName()
                                            + " matches ==> " + deviceNumbering);
                                    return new Pair<VirtualDisk, String>((VirtualDisk) device, deviceNumbering);
                                }
                            }

                            diskBackingInfo = diskBackingInfo.getParent();
                        } while (diskBackingInfo != null);
                    }
                }
            }
        }

        return null;
    }

    public String getDiskCurrentTopBackingFileInChain(String deviceBusName) throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");
        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualDisk) {
                    s_logger.info("Test against disk device, controller key: " + device.getControllerKey()
                            + ", unit number: " + device.getUnitNumber());

                    VirtualDeviceBackingInfo backingInfo = ((VirtualDisk) device).getBacking();
                    if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
                        VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;

                        String deviceNumbering = getDeviceBusName(devices, device);
                        if (deviceNumbering.equals(deviceBusName))
                            return diskBackingInfo.getFileName();
                    }
                }
            }
        }

        return null;
    }

    public VirtualDisk getDiskDeviceByDeviceBusName(String deviceBusName) throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualDisk) {
                    String deviceNumbering = getDeviceBusName(devices, device);
                    if (deviceNumbering.equals(deviceBusName))
                        return (VirtualDisk) device;
                }
            }
        }

        return null;
    }

    public VirtualMachineDiskInfoBuilder getDiskInfoBuilder() throws Exception {
        VirtualMachineDiskInfoBuilder builder = new VirtualMachineDiskInfoBuilder();

        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualDisk) {
                    VirtualDeviceBackingInfo backingInfo = ((VirtualDisk) device).getBacking();
                    if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
                        VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;

                        while (diskBackingInfo != null) {
                            String deviceBusName = getDeviceBusName(devices, device);
                            builder.addDisk(deviceBusName, diskBackingInfo.getFileName());
                            diskBackingInfo = diskBackingInfo.getParent();
                        }
                    }
                }
            }
        }

        return builder;
    }

    @Deprecated
    public List<Pair<String, ManagedObjectReference>> getDiskDatastorePathChain(VirtualDisk disk,
            boolean followChain) throws Exception {
        VirtualDeviceBackingInfo backingInfo = disk.getBacking();
        if (!(backingInfo instanceof VirtualDiskFlatVer2BackingInfo)) {
            throw new Exception("Unsupported VirtualDeviceBackingInfo");
        }

        List<Pair<String, ManagedObjectReference>> pathList = new ArrayList<Pair<String, ManagedObjectReference>>();
        VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;

        if (!followChain) {
            pathList.add(new Pair<String, ManagedObjectReference>(diskBackingInfo.getFileName(),
                    diskBackingInfo.getDatastore()));
            return pathList;
        }

        Pair<DatacenterMO, String> dcPair = getOwnerDatacenter();
        VirtualMachineFileInfo vmFilesInfo = getFileInfo();
        DatastoreFile snapshotDirFile = new DatastoreFile(vmFilesInfo.getSnapshotDirectory());
        DatastoreFile vmxDirFile = new DatastoreFile(vmFilesInfo.getVmPathName());

        do {
            if (diskBackingInfo.getParent() != null) {
                pathList.add(new Pair<String, ManagedObjectReference>(diskBackingInfo.getFileName(),
                        diskBackingInfo.getDatastore()));
                diskBackingInfo = diskBackingInfo.getParent();
            } else {
                // try getting parent info from VMDK file itself
                byte[] content = null;
                try {
                    String url = getContext().composeDatastoreBrowseUrl(dcPair.second(),
                            diskBackingInfo.getFileName());
                    content = getContext().getResourceContent(url);
                    if (content == null || content.length == 0) {
                        break;
                    }

                    pathList.add(new Pair<String, ManagedObjectReference>(diskBackingInfo.getFileName(),
                            diskBackingInfo.getDatastore()));
                } catch (Exception e) {
                    // if snapshot directory has been changed to place other than default. VMware has a bug
                    // that its corresponding disk backing info is not updated correctly. therefore, we will try search
                    // in snapshot directory one more time
                    DatastoreFile currentFile = new DatastoreFile(diskBackingInfo.getFileName());
                    String vmdkFullDsPath = snapshotDirFile.getCompanionPath(currentFile.getFileName());

                    String url = getContext().composeDatastoreBrowseUrl(dcPair.second(), vmdkFullDsPath);
                    content = getContext().getResourceContent(url);
                    if (content == null || content.length == 0) {
                        break;
                    }

                    pathList.add(new Pair<String, ManagedObjectReference>(vmdkFullDsPath,
                            diskBackingInfo.getDatastore()));
                }

                VmdkFileDescriptor descriptor = new VmdkFileDescriptor();
                descriptor.parse(content);
                if (descriptor.getParentFileName() != null && !descriptor.getParentFileName().isEmpty()) {
                    // create a fake one
                    VirtualDiskFlatVer2BackingInfo parentDiskBackingInfo = new VirtualDiskFlatVer2BackingInfo();
                    parentDiskBackingInfo.setDatastore(diskBackingInfo.getDatastore());

                    String parentFileName = descriptor.getParentFileName();
                    if (parentFileName.startsWith("/")) {
                        int fileNameStartPos = parentFileName.lastIndexOf("/");
                        parentFileName = parentFileName.substring(fileNameStartPos + 1);
                        parentDiskBackingInfo.setFileName(vmxDirFile.getCompanionPath(parentFileName));
                    } else {
                        parentDiskBackingInfo.setFileName(snapshotDirFile.getCompanionPath(parentFileName));
                    }
                    diskBackingInfo = parentDiskBackingInfo;
                } else {
                    break;
                }
            }
        } while (diskBackingInfo != null);

        return pathList;
    }

    private String getDeviceBusName(List<VirtualDevice> allDevices, VirtualDevice theDevice) throws Exception {
        for (VirtualDevice device : allDevices) {
            if (device.getKey() == theDevice.getControllerKey().intValue()) {
                if (device instanceof VirtualIDEController) {
                    return String.format("ide%d:%d", ((VirtualIDEController) device).getBusNumber(),
                            theDevice.getUnitNumber());
                } else if (device instanceof VirtualSCSIController) {
                    return String.format("scsi%d:%d", ((VirtualSCSIController) device).getBusNumber(),
                            theDevice.getUnitNumber());
                } else {
                    throw new Exception("Device controller is not supported yet");
                }
            }
        }
        throw new Exception("Unable to find device controller");
    }

    public List<VirtualDisk> getVirtualDisks() throws Exception {
        List<VirtualDisk> virtualDisks = new ArrayList<VirtualDisk>();

        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        for (VirtualDevice device : devices) {
            if (device instanceof VirtualDisk) {
                virtualDisks.add((VirtualDisk) device);
            }
        }

        return virtualDisks;
    }

    public List<String> detachAllDisksExcept(String vmdkBaseName, String deviceBusName) throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec();
        List<String> detachedDiskFiles = new ArrayList<String>();

        for (VirtualDevice device : devices) {
            if (device instanceof VirtualDisk) {
                VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();

                VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) device
                        .getBacking();

                DatastoreFile dsBackingFile = new DatastoreFile(diskBackingInfo.getFileName());
                String backingBaseName = dsBackingFile.getFileBaseName();
                String deviceNumbering = getDeviceBusName(devices, device);
                if (backingBaseName.equalsIgnoreCase(vmdkBaseName)
                        || (deviceBusName != null && deviceBusName.equals(deviceNumbering))) {
                    continue;
                } else {
                    s_logger.info("Detach " + diskBackingInfo.getFileName() + " from " + getName());

                    detachedDiskFiles.add(diskBackingInfo.getFileName());

                    deviceConfigSpec.setDevice(device);
                    deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.REMOVE);

                    reConfigSpec.getDeviceChange().add(deviceConfigSpec);
                }
            }
        }

        if (detachedDiskFiles.size() > 0) {
            ManagedObjectReference morTask = _context.getService().reconfigVMTask(_mor, reConfigSpec);
            boolean result = _context.getVimClient().waitForTask(morTask);
            if (result) {
                _context.waitForTaskProgressDone(morTask);
            } else {
                s_logger.warn("Unable to reconfigure the VM to detach disks");
                throw new Exception("Unable to reconfigure the VM to detach disks");
            }
        }

        return detachedDiskFiles;
    }

    public List<VirtualDevice> getAllDeviceList() throws Exception {
        return _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");
    }

    public VirtualDisk[] getAllDiskDevice() throws Exception {
        List<VirtualDisk> deviceList = new ArrayList<VirtualDisk>();
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");
        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualDisk) {
                    deviceList.add((VirtualDisk) device);
                }
            }
        }

        return deviceList.toArray(new VirtualDisk[0]);
    }

    public VirtualDisk getDiskDeviceByBusName(List<VirtualDevice> allDevices, String busName) throws Exception {
        for (VirtualDevice device : allDevices) {
            if (device instanceof VirtualDisk) {
                VirtualDisk disk = (VirtualDisk) device;
                String diskBusName = getDeviceBusName(allDevices, disk);
                if (busName.equalsIgnoreCase(diskBusName))
                    return disk;
            }
        }

        return null;
    }

    public VirtualDisk[] getAllIndependentDiskDevice() throws Exception {
        List<VirtualDisk> independentDisks = new ArrayList<VirtualDisk>();
        VirtualDisk[] allDisks = getAllDiskDevice();
        if (allDisks.length > 0) {
            for (VirtualDisk disk : allDisks) {
                String diskMode = "";
                if (disk.getBacking() instanceof VirtualDiskFlatVer1BackingInfo) {
                    diskMode = ((VirtualDiskFlatVer1BackingInfo) disk.getBacking()).getDiskMode();
                } else if (disk.getBacking() instanceof VirtualDiskFlatVer2BackingInfo) {
                    diskMode = ((VirtualDiskFlatVer2BackingInfo) disk.getBacking()).getDiskMode();
                } else if (disk.getBacking() instanceof VirtualDiskRawDiskMappingVer1BackingInfo) {
                    diskMode = ((VirtualDiskRawDiskMappingVer1BackingInfo) disk.getBacking()).getDiskMode();
                } else if (disk.getBacking() instanceof VirtualDiskSparseVer1BackingInfo) {
                    diskMode = ((VirtualDiskSparseVer1BackingInfo) disk.getBacking()).getDiskMode();
                } else if (disk.getBacking() instanceof VirtualDiskSparseVer2BackingInfo) {
                    diskMode = ((VirtualDiskSparseVer2BackingInfo) disk.getBacking()).getDiskMode();
                }

                if (diskMode.indexOf("independent") != -1) {
                    independentDisks.add(disk);
                }
            }
        }

        return independentDisks.toArray(new VirtualDisk[0]);
    }

    public int tryGetIDEDeviceControllerKey() throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualIDEController) {
                    return ((VirtualIDEController) device).getKey();
                }
            }
        }

        return -1;
    }

    public int getIDEDeviceControllerKey() throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualIDEController) {
                    return ((VirtualIDEController) device).getKey();
                }
            }
        }

        assert (false);
        throw new Exception("IDE Controller Not Found");
    }

    public int getNextIDEDeviceNumber() throws Exception {
        int controllerKey = getIDEDeviceControllerKey();
        return getNextDeviceNumber(controllerKey);
    }

    public VirtualDevice getIsoDevice() throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");
        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualCdrom) {
                    return device;
                }
            }
        }
        return null;
    }

    public int getPCIDeviceControllerKey() throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualPCIController) {
                    return ((VirtualPCIController) device).getKey();
                }
            }
        }

        assert (false);
        throw new Exception("PCI Controller Not Found");
    }

    public int getNextPCIDeviceNumber() throws Exception {
        int controllerKey = getPCIDeviceControllerKey();
        return getNextDeviceNumber(controllerKey);
    }

    public int getNextDeviceNumber(int controllerKey) throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        List<Integer> existingUnitNumbers = new ArrayList<Integer>();
        int deviceNumber = 0;
        if (devices != null && devices.size() > 0) {
            for (VirtualDevice device : devices) {
                if (device.getControllerKey() != null && device.getControllerKey().intValue() == controllerKey) {
                    existingUnitNumbers.add(device.getUnitNumber());
                }
            }
        }
        while (true) {
            if (!existingUnitNumbers.contains(Integer.valueOf(deviceNumber))) {
                break;
            }
            ++deviceNumber;
        }
        return deviceNumber;
    }

    private List<VirtualDevice> getNicDevices(boolean sorted) throws Exception {
        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        List<VirtualDevice> nics = new ArrayList<VirtualDevice>();
        if (devices != null) {
            for (VirtualDevice device : devices) {
                if (device instanceof VirtualEthernetCard) {
                    nics.add(device);
                }
            }
        }

        if (sorted) {
            Collections.sort(nics, new Comparator<VirtualDevice>() {
                @Override
                public int compare(VirtualDevice arg0, VirtualDevice arg1) {
                    int unitNumber0 = arg0.getUnitNumber() != null ? arg0.getUnitNumber().intValue() : -1;
                    int unitNumber1 = arg1.getUnitNumber() != null ? arg1.getUnitNumber().intValue() : -1;
                    if (unitNumber0 < unitNumber1)
                        return -1;
                    else if (unitNumber0 > unitNumber1)
                        return 1;
                    return 0;
                }
            });
        }

        return nics;
    }

    public VirtualDevice[] getNicDevices() throws Exception {
        return getNicDevices(false).toArray(new VirtualDevice[0]);
    }

    public VirtualDevice getNicDeviceByIndex(int index) throws Exception {
        List<VirtualDevice> nics = getNicDevices(true);
        try {
            return nics.get(index);
        } catch (IndexOutOfBoundsException e) {
            // Not found
            return null;
        }
    }

    public Pair<Integer, VirtualDevice> getNicDeviceIndex(String networkNamePrefix) throws Exception {
        List<VirtualDevice> nics = getNicDevices(true);

        int index = 0;
        String attachedNetworkSummary;
        String dvPortGroupName;
        for (VirtualDevice nic : nics) {
            attachedNetworkSummary = ((VirtualEthernetCard) nic).getDeviceInfo().getSummary();
            if (attachedNetworkSummary.startsWith(networkNamePrefix)) {
                return new Pair<Integer, VirtualDevice>(new Integer(index), nic);
            } else if (attachedNetworkSummary.endsWith("DistributedVirtualPortBackingInfo.summary")
                    || attachedNetworkSummary.startsWith("DVSwitch")) {
                dvPortGroupName = getDvPortGroupName((VirtualEthernetCard) nic);
                if (dvPortGroupName != null && dvPortGroupName.startsWith(networkNamePrefix)) {
                    s_logger.debug("Found a dvPortGroup already associated with public NIC.");
                    return new Pair<Integer, VirtualDevice>(new Integer(index), nic);
                }
            }
            index++;
        }
        return new Pair<Integer, VirtualDevice>(new Integer(-1), null);
    }

    public String getDvPortGroupName(VirtualEthernetCard nic) throws Exception {
        VirtualEthernetCardDistributedVirtualPortBackingInfo dvpBackingInfo = (VirtualEthernetCardDistributedVirtualPortBackingInfo) nic
                .getBacking();
        DistributedVirtualSwitchPortConnection dvsPort = dvpBackingInfo.getPort();
        String dvPortGroupKey = dvsPort.getPortgroupKey();
        ManagedObjectReference dvPortGroupMor = new ManagedObjectReference();
        dvPortGroupMor.setValue(dvPortGroupKey);
        dvPortGroupMor.setType("DistributedVirtualPortgroup");
        return (String) _context.getVimClient().getDynamicProperty(dvPortGroupMor, "name");
    }

    public VirtualDevice[] getMatchedDevices(Class<?>[] deviceClasses) throws Exception {
        assert (deviceClasses != null);

        List<VirtualDevice> returnList = new ArrayList<VirtualDevice>();

        List<VirtualDevice> devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device");

        if (devices != null) {
            for (VirtualDevice device : devices) {
                for (Class<?> clz : deviceClasses) {
                    if (clz.isInstance(device)) {
                        returnList.add(device);
                        break;
                    }
                }
            }
        }

        return returnList.toArray(new VirtualDevice[0]);
    }

    public void mountToolsInstaller() throws Exception {
        _context.getService().mountToolsInstaller(_mor);
    }

    public boolean unmountToolsInstaller() throws Exception {
        // Monitor VM questions
        final Boolean[] flags = { false };
        final VirtualMachineMO vmMo = this;
        final boolean[] encounterQuestion = new boolean[1];
        encounterQuestion[0] = false;
        Future<?> future = MonitorServiceExecutor.submit(new Runnable() {
            @Override
            public void run() {
                s_logger.info("VM Question monitor started...");

                while (!flags[0]) {
                    try {
                        VirtualMachineRuntimeInfo runtimeInfo = vmMo.getRuntimeInfo();
                        VirtualMachineQuestionInfo question = runtimeInfo.getQuestion();
                        if (question != null) {
                            encounterQuestion[0] = true;
                            if (s_logger.isTraceEnabled()) {
                                s_logger.trace("Question id: " + question.getId());
                                s_logger.trace("Question text: " + question.getText());
                            }

                            if (question.getMessage() != null) {
                                for (VirtualMachineMessage msg : question.getMessage()) {
                                    if (s_logger.isTraceEnabled()) {
                                        s_logger.trace("msg id: " + msg.getId());
                                        s_logger.trace("msg text: " + msg.getText());
                                    }
                                    if ("msg.cdromdisconnect.locked".equalsIgnoreCase(msg.getId())) {
                                        s_logger.info(
                                                "Found that VM has a pending question that we need to answer programmatically, question id: "
                                                        + msg.getId()
                                                        + ", for safe operation we will automatically decline it");
                                        vmMo.answerVM(question.getId(), "1");
                                        break;
                                    }
                                }
                            } else if (question.getText() != null) {
                                String text = question.getText();
                                String msgId;
                                String msgText;
                                if (s_logger.isDebugEnabled()) {
                                    s_logger.debug("question text : " + text);
                                }
                                String[] tokens = text.split(":");
                                msgId = tokens[0];
                                msgText = tokens[1];
                                if ("msg.cdromdisconnect.locked".equalsIgnoreCase(msgId)) {
                                    s_logger.info(
                                            "Found that VM has a pending question that we need to answer programmatically, question id: "
                                                    + question.getId() + ". Message id : " + msgId
                                                    + ". Message text : " + msgText
                                                    + ", for safe operation we will automatically decline it.");
                                    vmMo.answerVM(question.getId(), "1");
                                }
                            }

                            ChoiceOption choice = question.getChoice();
                            if (choice != null) {
                                for (ElementDescription info : choice.getChoiceInfo()) {
                                    if (s_logger.isTraceEnabled()) {
                                        s_logger.trace("Choice option key: " + info.getKey());
                                        s_logger.trace("Choice option label: " + info.getLabel());
                                    }
                                }
                            }
                        }
                    } catch (Throwable e) {
                        s_logger.error("Unexpected exception: ", e);
                    }

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

                s_logger.info("VM Question monitor stopped");
            }
        });

        try {
            _context.getService().unmountToolsInstaller(_mor);
        } finally {
            flags[0] = true;
            future.cancel(true);
        }
        if (encounterQuestion[0]) {
            s_logger.warn("cdrom is locked by VM. Failed to detach the ISO.");
            return false;
        } else {
            s_logger.info("Successfully unmounted tools installer from VM.");
            return true;
        }
    }

    public void redoRegistration(ManagedObjectReference morHost) throws Exception {
        String vmName = getVmName();
        VirtualMachineFileInfo vmFileInfo = getFileInfo();
        boolean isTemplate = isTemplate();

        HostMO hostMo;
        if (morHost != null)
            hostMo = new HostMO(getContext(), morHost);
        else
            hostMo = getRunningHost();

        ManagedObjectReference morFolder = getParentMor();
        ManagedObjectReference morPool = hostMo.getHyperHostOwnerResourcePool();

        _context.getService().unregisterVM(_mor);

        ManagedObjectReference morTask = _context.getService().registerVMTask(morFolder, vmFileInfo.getVmPathName(),
                vmName, false, morPool, hostMo.getMor());

        boolean result = _context.getVimClient().waitForTask(morTask);
        if (!result) {
            throw new Exception(
                    "Unable to register template due to " + TaskMO.getTaskFailureInfo(_context, morTask));
        } else {
            _context.waitForTaskProgressDone(morTask);
            if (isTemplate) {
                VirtualMachineMO vmNewRegistration = hostMo.findVmOnHyperHost(vmName);
                assert (vmNewRegistration != null);
                vmNewRegistration.markAsTemplate();
            }
        }
    }

    public long getHotAddMemoryIncrementSizeInMb() throws Exception {
        return (Long) _context.getVimClient().getDynamicProperty(_mor, "config.hotPlugMemoryIncrementSize");
    }

    public long getHotAddMemoryLimitInMb() throws Exception {
        return (Long) _context.getVimClient().getDynamicProperty(_mor, "config.hotPlugMemoryLimit");
    }

    public int getCoresPerSocket() throws Exception {
        // number of cores per socket is 1 in case of ESXi. It's not defined explicitly and the property is support since vSphere API 5.0.
        String apiVersion = HypervisorHostHelper.getVcenterApiVersion(_context);
        if (apiVersion.compareTo("5.0") < 0) {
            return 1;
        }
        Integer coresPerSocket = (Integer) _context.getVimClient().getDynamicProperty(_mor,
                "config.hardware.numCoresPerSocket");
        return coresPerSocket != null ? coresPerSocket : 1;
    }

    public int getVirtualHardwareVersion() throws Exception {
        VirtualHardwareOption vhOption = getVirtualHardwareOption();
        return vhOption.getHwVersion();
    }

    public VirtualHardwareOption getVirtualHardwareOption() throws Exception {
        VirtualMachineConfigOption vmConfigOption = _context.getService().queryConfigOption(getEnvironmentBrowser(),
                null, null);
        return vmConfigOption.getHardwareOptions();
    }

    private ManagedObjectReference getEnvironmentBrowser() throws Exception {
        if (_vmEnvironmentBrowser == null) {
            _vmEnvironmentBrowser = _context.getVimClient().getMoRefProp(_mor, "environmentBrowser");
        }
        return _vmEnvironmentBrowser;
    }

    public boolean isCpuHotAddSupported(String guestOsId) throws Exception {
        boolean guestOsSupportsCpuHotAdd = false;
        boolean virtualHardwareSupportsCpuHotAdd = false;
        GuestOsDescriptor guestOsDescriptor;
        int virtualHardwareVersion;
        int numCoresPerSocket;

        guestOsDescriptor = getGuestOsDescriptor(guestOsId);
        virtualHardwareVersion = getVirtualHardwareVersion();

        // Check if guest operating system supports cpu hotadd
        if (guestOsDescriptor.isSupportsCpuHotAdd()) {
            guestOsSupportsCpuHotAdd = true;
        }

        // Check if virtual machine is using hardware version 8 or later.
        // If hardware version is 7, then only 1 core per socket is supported. Hot adding multi-core vcpus is not allowed if hardware version is 7.
        if (virtualHardwareVersion >= 8) {
            virtualHardwareSupportsCpuHotAdd = true;
        } else if (virtualHardwareVersion == 7) {
            // Check if virtual machine has only 1 core per socket.
            numCoresPerSocket = getCoresPerSocket();
            if (numCoresPerSocket == 1) {
                virtualHardwareSupportsCpuHotAdd = true;
            }
        }
        return guestOsSupportsCpuHotAdd && virtualHardwareSupportsCpuHotAdd;
    }

    public boolean isMemoryHotAddSupported(String guestOsId) throws Exception {
        boolean guestOsSupportsMemoryHotAdd = false;
        boolean virtualHardwareSupportsMemoryHotAdd = false;
        GuestOsDescriptor guestOsDescriptor;
        int virtualHardwareVersion;

        guestOsDescriptor = getGuestOsDescriptor(guestOsId);
        virtualHardwareVersion = getVirtualHardwareVersion();

        // Check if guest operating system supports memory hotadd
        if (guestOsDescriptor.isSupportsMemoryHotAdd()) {
            guestOsSupportsMemoryHotAdd = true;
        }
        // Check if virtual machine is using hardware version 7 or later.
        if (virtualHardwareVersion >= 7) {
            virtualHardwareSupportsMemoryHotAdd = true;
        }
        return guestOsSupportsMemoryHotAdd && virtualHardwareSupportsMemoryHotAdd;
    }
}