org.jenkinsci.plugins.vsphere.tools.VSphere.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.plugins.vsphere.tools.VSphere.java

Source

/*   Copyright 2013, MANDIANT, Eric Lordahl
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package org.jenkinsci.plugins.vsphere.tools;

import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.logging.Level;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import org.apache.commons.lang.StringUtils;

import com.vmware.vim25.CustomizationSpecItem;
import com.vmware.vim25.GuestInfo;
import com.vmware.vim25.InvalidProperty;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.OptionValue;
import com.vmware.vim25.RuntimeFault;
import com.vmware.vim25.TaskInfo;
import com.vmware.vim25.TaskInfoState;
import com.vmware.vim25.VirtualMachineCloneSpec;
import com.vmware.vim25.VirtualMachineConfigInfo;
import com.vmware.vim25.VirtualMachineConfigSpec;
import com.vmware.vim25.VirtualMachinePowerState;
import com.vmware.vim25.VirtualMachineQuestionInfo;
import com.vmware.vim25.VirtualMachineRelocateSpec;
import com.vmware.vim25.VirtualMachineSnapshotInfo;
import com.vmware.vim25.VirtualMachineSnapshotTree;
import com.vmware.vim25.VirtualMachineToolsStatus;
import com.vmware.vim25.mo.ClusterComputeResource;
import com.vmware.vim25.mo.CustomizationSpecManager;
import com.vmware.vim25.mo.Datastore;
import com.vmware.vim25.mo.Folder;
import com.vmware.vim25.mo.InventoryNavigator;
import com.vmware.vim25.mo.ManagedEntity;
import com.vmware.vim25.mo.ResourcePool;
import com.vmware.vim25.mo.ServerConnection;
import com.vmware.vim25.mo.ServiceInstance;
import com.vmware.vim25.mo.Task;
import com.vmware.vim25.mo.VirtualMachine;
import com.vmware.vim25.mo.VirtualMachineSnapshot;
import com.vmware.vim25.mo.Datacenter;
import com.vmware.vim25.mo.Network;
import com.vmware.vim25.mo.DistributedVirtualPortgroup;
import com.vmware.vim25.mo.DistributedVirtualSwitch;

public class VSphere {
    private final URL url;
    private final String session;
    private final static Logger LOGGER = Logger.getLogger(VSphere.class.getName());

    private VSphere(@Nonnull String url, @Nonnull String user, @CheckForNull String pw) throws VSphereException {
        try {
            //TODO - change ignoreCert to be configurable
            this.url = new URL(url);
            this.session = (new ServiceInstance(this.url, user, pw, true)).getServerConnection().getSessionStr();
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    private ServiceInstance getServiceInstance() throws RemoteException, MalformedURLException {
        return new ServiceInstance(url, session, true);
    }

    /**
     * Initiates Connection to vSphere Server
     * @param server Server URL
     * @param user Username.
     * @param pw Password.
     * @throws VSphereException If an error occurred.
     * @return A connected instance.
     */
    public static VSphere connect(@Nonnull String server, @Nonnull String user, @CheckForNull String pw)
            throws VSphereException {
        return new VSphere(server, user, pw);
    }

    /**
     * Disconnect from vSphere server.
     * <p>
     * Note: This logs any {@link Exception} it encounters - it does not pass
     * them to get to the calling method.
     * </p>
     */
    public void disconnect() {
        try {
            this.getServiceInstance().getServerConnection().logout();
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "Caught exception when trying to disconnect vSphere.", e);
        }
    }

    /**
     * Deploys a new VM from an existing (named) Template.
     *
     * @param cloneName - name of VM to be created
     * @param sourceName - name of VM or template to be cloned
     * @param linkedClone - true if you want to re-use disk backings
     * @param resourcePoolName - resource pool to use
     * @param cluster - ComputeClusterResource to use
     * @param datastoreName - Datastore to use
     * @param folderName - Folder name or path to use
     * @param powerOn - If true the VM will be powered on.
     * @param customizationSpec - Customization spec to use for this VM
     * @param jLogger - Where to log to.
     * @throws VSphereException If an error occurred.
     */
    public void deployVm(String cloneName, String sourceName, boolean linkedClone, String resourcePoolName,
            String cluster, String datastoreName, String folderName, boolean powerOn, String customizationSpec,
            PrintStream jLogger) throws VSphereException {
        final boolean useCurrentSnapshotIsFALSE = false;
        final String namedSnapshotIsNULL = null;
        final Map<String, String> extraConfigParameters = null;
        logMessage(jLogger, "Deploying new vm \"" + cloneName + "\" from template \"" + sourceName + "\"");
        cloneOrDeployVm(cloneName, sourceName, linkedClone, resourcePoolName, cluster, datastoreName, folderName,
                useCurrentSnapshotIsFALSE, namedSnapshotIsNULL, powerOn, extraConfigParameters, customizationSpec,
                jLogger);
    }

    /**
     * Clones a new VM from an existing (named) VM.
     *
     * @param cloneName - name of VM to be created
     * @param sourceName - name of VM or template to be cloned
     * @param linkedClone - true if you want to re-use disk backings
     * @param resourcePoolName - resource pool to use
     * @param cluster - ComputeClusterResource to use
     * @param datastoreName - Datastore to use
     * @param folderName - Folder name or path to use
     * @param powerOn - If true the VM will be powered on.
     * @param customizationSpec - Customization spec to use for this VM
     * @param jLogger - Where to log to.
     * @throws VSphereException If an error occurred.
     */
    public void cloneVm(String cloneName, String sourceName, boolean linkedClone, String resourcePoolName,
            String cluster, String datastoreName, String folderName, boolean powerOn, String customizationSpec,
            PrintStream jLogger) throws VSphereException {
        final boolean useCurrentSnapshotIsTRUE = true;
        final String namedSnapshotIsNULL = null;
        final Map<String, String> extraConfigParameters = null;
        logMessage(jLogger, "Creating a " + (linkedClone ? "shallow" : "deep") + " clone of \"" + sourceName
                + "\" to \"" + cloneName + "\"");
        cloneOrDeployVm(cloneName, sourceName, linkedClone, resourcePoolName, cluster, datastoreName, folderName,
                useCurrentSnapshotIsTRUE, namedSnapshotIsNULL, powerOn, extraConfigParameters, customizationSpec,
                jLogger);
    }

    /**
     * Creates a new VM by cloning an existing VM or Template.
     * 
     * @param cloneName
     *            The name for the new VM.
     * @param sourceName
     *            The name of the VM or Template that is to be cloned.
     * @param linkedClone
     *            If true then the clone will be defined as a delta from the
     *            original, rather than a "full fat" copy. If this is true then
     *            you will need to use a snapshot.
     * @param resourcePoolName
     *            (Optional) The name of the resource pool to use, or null.
     * @param cluster
     *            (Optional) The name of the cluster, or null.
     * @param datastoreName
     *            (Optional) The name of the data store, or null.
     * @param folderName
     *            (Optional) The name or path of the VSphere folder, or null
     * @param useCurrentSnapshot
     *            If true then the clone will be created from the source VM's
     *            "current" snapshot. This means that the VM <em>must</em> have
     *            at least one snapshot.
     * @param namedSnapshot
     *            If set then the clone will be created from the source VM's
     *            snapshot of this name. If this is set then
     *            <code>useCurrentSnapshot</code> must not be set.
     * @param powerOn
     *            If true then the new VM will be switched on after it has been
     *            created.
     * @param extraConfigParameters
     *            (Optional) parameters to set in the VM's "extra config"
     *            object. This data can then be read back at a later stage.In
     *            the case of parameters whose name starts "guestinfo.", the
     *            parameter can be read by the VMware Tools on the client OS.
     *            e.g. a variable named "guestinfo.Foo" with value "Bar" could
     *            be read on the guest using the command-line
     *            <tt>vmtoolsd --cmd "info-get guestinfo.Foo"</tt>.
     * @param customizationSpec
     *            (Optional) Customization spec to use for this VM, or null
     * @param jLogger
     *            Where to log to.
     * @throws VSphereException
     *             if anything goes wrong.
     */
    public void cloneOrDeployVm(String cloneName, String sourceName, boolean linkedClone, String resourcePoolName,
            String cluster, String datastoreName, String folderName, boolean useCurrentSnapshot,
            final String namedSnapshot, boolean powerOn, Map<String, String> extraConfigParameters,
            String customizationSpec, PrintStream jLogger) throws VSphereException {
        try {
            final VirtualMachine sourceVm = getVmByName(sourceName);
            if (sourceVm == null) {
                throw new VSphereNotFoundException("VM or template", sourceName);
            }
            if (getVmByName(cloneName) != null) {
                throw new VSphereDuplicateException("VM", cloneName);
            }

            final VirtualMachineConfigInfo vmConfig = sourceVm.getConfig();
            final boolean sourceIsATemplate = vmConfig.template;
            final String sourceType = sourceIsATemplate ? "Template" : "VM";
            final VirtualMachineRelocateSpec rel = createRelocateSpec(jLogger, linkedClone, resourcePoolName,
                    cluster, datastoreName, sourceIsATemplate);
            final VirtualMachineCloneSpec cloneSpec = createCloneSpec(rel);
            cloneSpec.setTemplate(false);
            cloneSpec.powerOn = powerOn;

            if (namedSnapshot != null && !namedSnapshot.isEmpty()) {
                if (useCurrentSnapshot) {
                    throw new IllegalArgumentException("It is not valid to request a clone of " + sourceType
                            + "  \"" + sourceName + "\" based on its snapshot \"" + namedSnapshot
                            + "\" AND also specify that the latest snapshot should be used.  Either choose to use the latest snapshot, or name a snapshot, or neither, but not both.");
                }
                final VirtualMachineSnapshot namedVMSnapshot = getSnapshotInTree(sourceVm, namedSnapshot);
                if (namedVMSnapshot == null) {
                    throw new VSphereNotFoundException("Snapshot", namedSnapshot, "Source " + sourceType + "  \""
                            + sourceName + "\" has no snapshot called \"" + namedSnapshot + "\".");
                }
                logMessage(jLogger, "Clone of " + sourceType + " \"" + sourceName
                        + "\" will be based on named snapshot \"" + namedSnapshot + "\".");
                cloneSpec.setSnapshot(namedVMSnapshot.getMOR());
            }
            if (useCurrentSnapshot) {
                final VirtualMachineSnapshot currentSnapShot = sourceVm.getCurrentSnapShot();
                if (currentSnapShot == null) {
                    throw new VSphereNotFoundException("Snapshot", null,
                            "Source " + sourceType + "  \"" + sourceName + "\" requires at least one snapshot.");
                }
                logMessage(jLogger, "Clone of " + sourceType + " \"" + sourceName
                        + "\" will be based on current snapshot \"" + currentSnapShot.toString() + "\".");
                cloneSpec.setSnapshot(currentSnapShot.getMOR());
            }
            if (extraConfigParameters != null && !extraConfigParameters.isEmpty()) {
                logMessage(jLogger, "Clone of " + sourceType + " \"" + sourceName
                        + "\" will have extra configuration parameters " + extraConfigParameters + ".");
                VirtualMachineConfigSpec cs = createVMConfigSpecFromExtraConfigParameters(extraConfigParameters);
                cloneSpec.setConfig(cs);
            }
            if (customizationSpec != null && customizationSpec.length() > 0) {
                logMessage(jLogger, "Clone of " + sourceType + " \"" + sourceName
                        + "\" will use customization specification \"" + customizationSpec + "\".");
                CustomizationSpecItem spec = getCustomizationSpecByName(customizationSpec);
                cloneSpec.setCustomization(spec.getSpec());
            }

            Folder folder;
            if (folderName == null || folderName.isEmpty() || folderName.equals(" ")) {
                //same folder as source
                folder = (Folder) sourceVm.getParent();
            } else if (!folderExists(folderName)) {
                folder = (Folder) sourceVm.getParent();
                logMessage(jLogger,
                        "Unable to find the specified folder. Creating VM in the same folder as its parent ");
            } else {
                folder = getFolder(folderName);
            }

            final Task task = sourceVm.cloneVM_Task(folder, cloneName, cloneSpec);
            logMessage(jLogger, "Started cloning of " + sourceType + " \"" + sourceName + "\". Please wait ...");

            final String status = task.waitForTask();
            if (!TaskInfoState.success.toString().equals(status)) {
                throw newVSphereException(task.getTaskInfo(),
                        "Couldn't clone \"" + sourceName + "\". " + "Clone task ended with status " + status + ".");
            }
            logMessage(jLogger, "Successfully cloned VM \"" + sourceName + "\" to create \"" + cloneName + "\".");
        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    private VirtualMachineCloneSpec createCloneSpec(VirtualMachineRelocateSpec rel) {
        VirtualMachineCloneSpec cloneSpec = new VirtualMachineCloneSpec();
        cloneSpec.setLocation(rel);
        cloneSpec.setTemplate(false);
        cloneSpec.setPowerOn(true);
        return cloneSpec;
    }

    private VirtualMachineRelocateSpec createRelocateSpec(PrintStream jLogger, boolean linkedClone,
            String resourcePoolName, String cluster, String datastoreName, boolean isResourcePoolRequired)
            throws RemoteException, MalformedURLException, VSphereException {
        VirtualMachineRelocateSpec rel = new VirtualMachineRelocateSpec();

        if (linkedClone) {
            rel.setDiskMoveType("createNewChildDiskBacking");
        } else {
            rel.setDiskMoveType("moveAllDiskBackingsAndDisallowSharing");
        }

        ClusterComputeResource clusterResource = getClusterByName(cluster);

        // probably only of interest if someone actually entered a cluster name
        if (clusterResource == null && StringUtils.isNotBlank(cluster)) {
            logMessage(jLogger, "Cluster resource " + cluster
                    + " does not exist, root folder will be used for getting resource pool and datastore");
        }

        if (resourcePoolName != null && !resourcePoolName.isEmpty()) {
            ResourcePool resourcePool = getResourcePoolByName(resourcePoolName, clusterResource);
            if (resourcePool == null) {
                throw new VSphereNotFoundException("Resource pool", resourcePoolName);
            }
            rel.setPool(resourcePool.getMOR());
        } else if (isResourcePoolRequired) {
            throw new VSphereException("You must specify a resource  pool  when using a template");
        }

        if (datastoreName != null && !datastoreName.isEmpty()) {
            Datastore datastore = getDatastoreByName(datastoreName, clusterResource);
            if (datastore == null) {
                throw new VSphereNotFoundException("Datastore", datastoreName);
            }
            rel.setDatastore(datastore.getMOR());
        }
        return rel;
    }

    public void reconfigureVm(String name, VirtualMachineConfigSpec spec) throws VSphereException {
        VirtualMachine vm = getVmByName(name);
        if (vm == null) {
            throw new VSphereNotFoundException("VM or template", name);
        }
        LOGGER.log(Level.FINER, "Reconfiguring VM. Please wait ...");
        try {
            Task task = vm.reconfigVM_Task(spec);
            String status = task.waitForTask();
            if (status.equals(TaskInfoState.success.toString())) {
                return;
            }
            throw newVSphereException(task.getTaskInfo(), "Couldn't reconfigure \"" + name + "\"!");
        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException("VM cannot be reconfigured:" + e.getMessage(), e);
        }
    }

    /**
     * @param name - Name of VM to start
     * @param timeoutInSeconds How long to wait for the VM to be running.
     * @throws VSphereException If an error occurred.
     */
    public void startVm(String name, int timeoutInSeconds) throws VSphereException {
        try {
            VirtualMachine vm = getVmByName(name);
            if (vm == null) {
                throw new VSphereNotFoundException("VM", name);
            }
            if (isPoweredOn(vm))
                return;

            if (vm.getConfig().template)
                throw new VSphereException("VM represents a template!");

            Task task = vm.powerOnVM_Task(null);

            int timesToCheck = timeoutInSeconds / 5;
            // add one extra time for remainder
            timesToCheck++;
            LOGGER.log(Level.FINER, "Checking " + timesToCheck + " times for vm to be powered on");

            for (int i = 0; i < timesToCheck; i++) {
                if (task.getTaskInfo().getState() == TaskInfoState.success) {
                    LOGGER.log(Level.FINER, "VM was powered up successfully.");
                    return;
                }
                if (task.getTaskInfo().getState() == TaskInfoState.running
                        || task.getTaskInfo().getState() == TaskInfoState.queued) {
                    Thread.sleep(5000);
                }
                //Check for copied/moved question
                VirtualMachineQuestionInfo q = vm.getRuntime().getQuestion();
                if (q != null && q.getId().equals("_vmx1")) {
                    vm.answerVM(q.getId(), q.getChoice().getDefaultIndex().toString());
                    return;
                }
            }
        } catch (InterruptedException e) { // build aborted
            Thread.currentThread().interrupt(); // pass interrupt upwards
            throw new VSphereException("VM cannot be started: " + e.getMessage(), e);
        } catch (Exception e) {
            throw new VSphereException("VM cannot be started: " + e.getMessage(), e);
        }

        throw new VSphereException("VM cannot be started");
    }

    private ManagedObjectReference findSnapshotInTree(VirtualMachineSnapshotTree[] snapTree, String snapName) {
        LOGGER.log(Level.FINER, "Looking for snapshot " + snapName);
        for (VirtualMachineSnapshotTree node : snapTree) {
            if (snapName.equals(node.getName())) {
                return node.getSnapshot();
            } else {
                VirtualMachineSnapshotTree[] childTree = node.getChildSnapshotList();
                if (childTree != null) {
                    ManagedObjectReference mor = findSnapshotInTree(childTree, snapName);
                    if (mor != null) {
                        return mor;
                    }
                }
            }
        }
        return null;
    }

    public VirtualMachineSnapshot getSnapshotInTree(VirtualMachine vm, String snapName) {
        if (vm == null || snapName == null) {
            return null;
        }

        LOGGER.log(Level.FINER, "Looking for snapshot " + snapName + " in " + vm.getName());
        VirtualMachineSnapshotInfo info = vm.getSnapshot();
        if (info != null) {
            VirtualMachineSnapshotTree[] snapTree = info.getRootSnapshotList();
            if (snapTree != null) {
                ManagedObjectReference mor = findSnapshotInTree(snapTree, snapName);
                if (mor != null) {
                    return new VirtualMachineSnapshot(vm.getServerConnection(), mor);
                }
            }
        }

        return null;
    }

    public void revertToSnapshot(String vmName, String snapName) throws VSphereException {

        VirtualMachine vm = getVmByName(vmName);
        VirtualMachineSnapshot snap = getSnapshotInTree(vm, snapName);

        if (snap == null) {
            LOGGER.log(Level.SEVERE,
                    "Cannot find snapshot: '" + snapName + "' for virtual machine: '" + vm.getName() + "'");
            throw new VSphereNotFoundException("Snapshot", snapName);
        }

        try {
            Task task = snap.revertToSnapshot_Task(null);
            if (!task.waitForTask().equals(Task.SUCCESS)) {
                final String msg = "Could not revert to snapshot '" + snap.toString() + "' for virtual machine:'"
                        + vm.getName() + "'";
                LOGGER.log(Level.SEVERE, msg);
                throw newVSphereException(task.getTaskInfo(), msg);
            }
        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    public void deleteSnapshot(String vmName, String snapName, boolean consolidate, boolean failOnNoExist)
            throws VSphereException {

        VirtualMachine vm = getVmByName(vmName);
        VirtualMachineSnapshot snap = getSnapshotInTree(vm, snapName);

        if (snap == null && failOnNoExist) {
            throw new VSphereNotFoundException("Snapshot", snapName);
        }

        try {
            Task task;
            if (snap != null) {
                //Does not delete subtree; Implicitly consolidates disk
                task = snap.removeSnapshot_Task(false);
                if (!task.waitForTask().equals(Task.SUCCESS)) {
                    throw newVSphereException(task.getTaskInfo(), "Could not delete snapshot");
                }
            }

            if (!consolidate)
                return;

            //This might be redundant, but I think it consolidates all disks,
            //where as the removeSnapshot only consolidates the individual disk
            task = vm.consolidateVMDisks_Task();
            if (!task.waitForTask().equals(Task.SUCCESS)) {
                throw newVSphereException(task.getTaskInfo(), "Could not consolidate VM disks");
            }
        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    public void takeSnapshot(String vmName, String snapshot, String description, boolean snapMemory)
            throws VSphereException {

        final String message = "Could not take snapshot";
        VirtualMachine vmToSnapshot = getVmByName(vmName);
        if (vmToSnapshot == null) {
            throw new VSphereNotFoundException("VM", vmName);
        }
        try {
            Task task = vmToSnapshot.createSnapshot_Task(snapshot, description, snapMemory, !snapMemory);
            if (task.waitForTask().equals(Task.SUCCESS)) {
                return;
            }
            throw newVSphereException(task.getTaskInfo(), message);
        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(message, e);
        }
    }

    public void markAsTemplate(String vmName, String snapName, boolean force) throws VSphereException {

        final String message = "Could not mark as Template. Check it's power state or select \"force.\"";
        try {
            VirtualMachine vm = getVmByName(vmName);
            if (vm.getConfig().template)
                return;

            if (isPoweredOff(vm) || force) {
                powerOffVm(vm, force, false);
                vm.markAsTemplate();
                return;
            }
        } catch (Exception e) {
            throw new VSphereException(message, e);
        }
        throw new VSphereException(message);
    }

    public void markAsVm(String name, String resourcePool, String cluster) throws VSphereException {
        try {
            VirtualMachine vm = getVmByName(name);
            if (vm.getConfig().template) {
                vm.markAsVirtualMachine(getResourcePoolByName(resourcePool, getClusterByName(cluster)), null);
            }
        } catch (Exception e) {
            throw new VSphereException("Could not convert to VM", e);
        }
    }

    /**
     * Asks vSphere for the IP address used by a VM.
     * 
     * @param vm VirtualMachine name whose IP is to be returned.
     * @param timeout How long to wait (in seconds) for the IP address to known to vSphere.
     * @return String containing IP address.
     * @throws VSphereException If an error occurred.
     */
    public String getIp(VirtualMachine vm, int timeout) throws VSphereException {

        if (vm == null)
            throw new VSphereException("VM is null");

        //Determine how many attempts will be made to fetch the IP address
        final int waitSeconds = 5;
        final int maxTries;
        if (timeout <= waitSeconds)
            maxTries = 1;
        else
            maxTries = (int) Math.round((double) timeout / waitSeconds);

        for (int count = 0; count < maxTries; count++) {

            GuestInfo guestInfo = vm.getGuest();

            // guest info can be null sometimes
            if (guestInfo != null && guestInfo.getIpAddress() != null) {
                return guestInfo.getIpAddress();
            }

            try {
                //wait
                Thread.sleep(waitSeconds * 1000);
            } catch (InterruptedException e) { // build aborted
                Thread.currentThread().interrupt(); // pass interrupt upwards
                break; // and abort our activities now.
            }
        }
        return null;
    }

    /**
     * @param vmName - name of VM object to retrieve
     * @return - VirtualMachine object
     * @throws VSphereException If an error occurred.
     */
    public VirtualMachine getVmByName(String vmName) throws VSphereException {
        try {
            return (VirtualMachine) new InventoryNavigator(getServiceInstance().getRootFolder())
                    .searchManagedEntity("VirtualMachine", vmName);
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    public int countVms() throws VSphereException {
        int count = 0;
        try {
            final InventoryNavigator navigator = new InventoryNavigator(getServiceInstance().getRootFolder());
            final ManagedEntity[] entities = navigator.searchManagedEntities(false);
            count = entities.length;
        } catch (Exception ex) {
            throw new VSphereException(ex);
        }
        return count;
    }

    public int countVmsByPrefix(final String prefix) throws VSphereException {
        int count = 0;
        try {
            final InventoryNavigator navigator = new InventoryNavigator(getServiceInstance().getRootFolder());
            final ManagedEntity[] entities = navigator.searchManagedEntities(false);
            for (final ManagedEntity entity : entities) {
                if (entity.getName().startsWith(prefix)) {
                    ++count;
                }
            }
        } catch (Exception ex) {
            throw new VSphereException(ex);
        }
        return count;
    }

    private Datastore getDatastoreByName(final String datastoreName, ManagedEntity rootEntity)
            throws RemoteException, MalformedURLException {
        if (rootEntity == null) {
            rootEntity = getServiceInstance().getRootFolder();
        }

        Datastore datastore = (Datastore) new InventoryNavigator(rootEntity).searchManagedEntity("Datastore",
                datastoreName);
        if (datastore != null) {
            return datastore;
        }

        if (rootEntity == null || !(rootEntity instanceof ClusterComputeResource)) {
            return null;
        }

        // try to fetch data store directly from cluster if above approach doesn't work
        ClusterComputeResource clusterResource = (ClusterComputeResource) rootEntity;

        for (Datastore dataStore : clusterResource.getDatastores()) {
            if (dataStore.getName().equals(datastoreName)) {
                return dataStore;
            }
        }
        return null;
    }

    /*
     Check if folder exists along all the vSphere folders
     */
    public Boolean folderExists(String folderPath) throws VSphereException {
        try {
            String[] folderHierarchy = folderPath.split("/");
            ManagedEntity folder = null;

            for (int i = 0; i < folderHierarchy.length; i++) {
                if (i == 0) {
                    folder = new InventoryNavigator(getServiceInstance().getRootFolder())
                            .searchManagedEntity("Folder", folderHierarchy[i]);
                } else {
                    folder = new InventoryNavigator(folder).searchManagedEntity(null, folderHierarchy[i]);
                }
                if (folder == null) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Failed while checking if folder exists");
            throw new VSphereException(e);
        }
    }

    public Folder getFolder(String folderPath) throws VSphereException {
        try {
            String[] folderHierarchy = folderPath.split("/");
            ManagedEntity folder = null;

            for (int i = 0; i < folderHierarchy.length; i++) {
                if (i == 0) {
                    folder = new InventoryNavigator(getServiceInstance().getRootFolder())
                            .searchManagedEntity("Folder", folderHierarchy[i]);
                } else {
                    folder = new InventoryNavigator(folder).searchManagedEntity(null, folderHierarchy[i]);
                }
            }
            return (Folder) folder;
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Invalid folder");
            throw new VSphereException(e);
        }
    }

    public CustomizationSpecItem getCustomizationSpecByName(final String customizationSpecName)
            throws VSphereException {
        try {
            ServerConnection conn = getServiceInstance().getServerConnection();
            CustomizationSpecManager mgr = new CustomizationSpecManager(conn,
                    getServiceInstance().getServiceContent().customizationSpecManager);

            return mgr.getCustomizationSpec(customizationSpecName);
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    /**
     * @return - ManagedEntity array of Datastore
     * @throws VSphereException If an error occurred.
     */
    public ManagedEntity[] getDatastores() throws VSphereException {
        try {
            return new InventoryNavigator(getServiceInstance().getRootFolder()).searchManagedEntities("Datastore");
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    /**
     * @param poolName - Name of pool to use
     * @return - ResourcePool object
     * @throws InvalidProperty
     * @throws RuntimeFault
     * @throws RemoteException
     * @throws MalformedURLException
     * @throws VSphereException
     */
    private ResourcePool getResourcePoolByName(final String poolName, ManagedEntity rootEntity)
            throws InvalidProperty, RuntimeFault, RemoteException, MalformedURLException {
        if (rootEntity == null)
            rootEntity = getServiceInstance().getRootFolder();

        return (ResourcePool) new InventoryNavigator(rootEntity).searchManagedEntity("ResourcePool", poolName);
    }

    /**
     * @param clusterName - Name of cluster name to find
     * @param rootEntity - managed entity to search
     * @return - ClusterComputeResource object
     * @throws InvalidProperty
     * @throws RuntimeFault
     * @throws RemoteException
     * @throws MalformedURLException 
     * @throws VSphereException 
     */
    private ClusterComputeResource getClusterByName(final String clusterName, ManagedEntity rootEntity)
            throws InvalidProperty, RuntimeFault, RemoteException, MalformedURLException {
        if (rootEntity == null)
            rootEntity = getServiceInstance().getRootFolder();

        return (ClusterComputeResource) new InventoryNavigator(rootEntity)
                .searchManagedEntity("ClusterComputeResource", clusterName);
    }

    /**
     * @param clusterName - Name of cluster name to find
     * @return - ClusterComputeResource object
     * @throws InvalidProperty
     * @throws RuntimeFault
     * @throws RemoteException
     * @throws MalformedURLException 
     * @throws VSphereException 
     */
    private ClusterComputeResource getClusterByName(final String clusterName)
            throws InvalidProperty, RuntimeFault, RemoteException, MalformedURLException {
        return getClusterByName(clusterName, null);
    }

    /**
     * Destroys the VM in vSphere
     * @param name - VM object to destroy
     * @param failOnNoExist If true and the VM does not exist then a {@link VSphereNotFoundException} will be thrown.
     * @throws VSphereException If an error occurred.
     */
    public void destroyVm(String name, boolean failOnNoExist) throws VSphereException {
        try {
            VirtualMachine vm = getVmByName(name);
            if (vm == null) {
                if (failOnNoExist)
                    throw new VSphereNotFoundException("VM", name);

                LOGGER.log(Level.FINER, "VM \"" + name + "\" does not exist, or already deleted!");
                return;
            }

            if (!vm.getConfig().template) {
                powerOffVm(vm, true, false);
            }

            final Task task = vm.destroy_Task();
            String status = task.waitForTask();
            if (status.equals(Task.SUCCESS)) {
                LOGGER.log(Level.FINER, "VM \"" + name + "\" was deleted successfully.");
                return;
            }
            throw newVSphereException(task.getTaskInfo(), "Could not delete VM \"" + name + "\"!");

        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(e.getMessage(), e);
        }
    }

    /**
     * Renames a VM Snapshot
     * @param vmName the name of the VM whose snapshot is being renamed.
     * @param oldName the current name of the VM's snapshot.
     * @param newName the new name of the VM's snapshot.
     * @param newDescription the new description of the VM's snapshot.
     * @throws VSphereException If an error occurred.
     */
    public void renameVmSnapshot(String vmName, String oldName, String newName, String newDescription)
            throws VSphereException {
        try {
            VirtualMachine vm = getVmByName(vmName);
            if (vm == null) {
                throw new VSphereNotFoundException("VM", vmName);
            }

            VirtualMachineSnapshot snapshot = getSnapshotInTree(vm, oldName);

            snapshot.renameSnapshot(newName, newDescription);

            LOGGER.log(Level.FINER, "VM Snapshot was renamed successfully.");
            return;

        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(e.getMessage(), e);
        }
    }

    /**
     * Renames the VM vSphere
     * @param oldName the current name of the vm
     * @param newName the new name of the vm
     * @throws VSphereException If an error occurred.
     */
    public void renameVm(String oldName, String newName) throws VSphereException {
        try {
            VirtualMachine vm = getVmByName(oldName);
            if (vm == null) {
                throw new VSphereNotFoundException("VM", oldName);
            }

            final Task task = vm.rename_Task(newName);
            final String status = task.waitForTask();
            if (status.equals(Task.SUCCESS)) {
                LOGGER.log(Level.FINER, "VM was renamed successfully.");
                return;
            }
            throw newVSphereException(task.getTaskInfo(), "Could not rename VM \"" + oldName + "\"!");

        } catch (RuntimeException | VSphereException e) {
            throw e;
        } catch (Exception e) {
            throw new VSphereException(e.getMessage(), e);
        }
    }

    private boolean isSuspended(VirtualMachine vm) {
        return (vm.getRuntime().getPowerState() == VirtualMachinePowerState.suspended);
    }

    private boolean isPoweredOn(VirtualMachine vm) {
        return (vm.getRuntime().getPowerState() == VirtualMachinePowerState.poweredOn);
    }

    private boolean isPoweredOff(VirtualMachine vm) {
        return (vm.getRuntime() != null && vm.getRuntime().getPowerState() == VirtualMachinePowerState.poweredOff);
    }

    public boolean vmToolIsEnabled(VirtualMachine vm) {
        VirtualMachineToolsStatus status = vm.getGuest().toolsStatus;
        return ((status == VirtualMachineToolsStatus.toolsOk) || (status == VirtualMachineToolsStatus.toolsOld));
    }

    public void powerOffVm(VirtualMachine vm, boolean evenIfSuspended, boolean shutdownGracefully)
            throws VSphereException {

        if (vm.getConfig().template)
            throw new VSphereException("VM represents a template!");

        if (isPoweredOn(vm) || (evenIfSuspended && isSuspended(vm))) {
            boolean doHardShutdown = true;

            String status;
            try {
                if (!isSuspended(vm) && shutdownGracefully && vmToolIsEnabled(vm)) {
                    LOGGER.log(Level.FINER, "Requesting guest shutdown");
                    vm.shutdownGuest();

                    // Wait for up to 180 seconds for a shutdown - then shutdown hard.
                    for (int i = 0; i <= 180; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) { // build aborted
                            Thread.currentThread().interrupt(); // pass interrupt upwards
                            throw new VSphereException("VM power-down interrupted", e);
                        }
                        if (isPoweredOff(vm)) {
                            doHardShutdown = false;
                            LOGGER.log(Level.FINER, "VM gracefully powered down successfully.");
                            return;
                        }
                    }
                }

                if (doHardShutdown) {
                    LOGGER.log(Level.FINER, "Powering off the VM");
                    final Task task = vm.powerOffVM_Task();
                    status = task.waitForTask();

                    if (status.equals(Task.SUCCESS)) {
                        LOGGER.log(Level.FINER, "VM was powered down successfully.");
                        return;
                    }
                    throw newVSphereException(task.getTaskInfo(), "Machine could not be powered down!");
                }
            } catch (RuntimeException | VSphereException e) {
                throw e;
            } catch (Exception e) {
                throw new VSphereException(e);
            }
        } else if (isPoweredOff(vm)) {
            LOGGER.log(Level.FINER, "Machine is already off.");
            return;
        }

        throw new VSphereException("Machine could not be powered down!");
    }

    public void suspendVm(VirtualMachine vm) throws VSphereException {
        if (isPoweredOn(vm)) {
            try {
                //TODO is this better?
                //vm.shutdownGuest()
                final Task task = vm.suspendVM_Task();
                final String status = task.waitForTask();
                if (Task.SUCCESS.equals(status)) {
                    LOGGER.log(Level.FINER, "VM was suspended successfully.");
                    return;
                }
                throw newVSphereException(task.getTaskInfo(), "Machine could not be suspended!");
            } catch (RuntimeException | VSphereException e) {
                throw e;
            } catch (Exception e) {
                throw new VSphereException(e);
            }
        } else {
            LOGGER.log(Level.FINER, "Machine not powered on.");
            return;
        }
    }

    /**
     * Private helper functions that finds the datanceter a VirtualMachine belongs to
     * @param managedEntity - VM object
     * @return returns Datacenter object
     */
    private Datacenter getDataCenter(ManagedEntity managedEntity) {
        if (managedEntity != null) {
            ManagedEntity parent = managedEntity.getParent();
            if (parent.getMOR().getType().equals("Datacenter")) {
                return (Datacenter) parent;
            } else {
                return getDataCenter(managedEntity.getParent());
            }
        } else {
            return null;
        }
    }

    /**
     * Find Distributed Virtual Port Group name in the same Datacenter as the VM
     * @param virtualMachine - VM object
     * @param name - the name of the Port Group
     * @return returns DistributedVirtualPortgroup object for the provided vDS PortGroup
     * @throws VSphereException If an error occurred.
     */
    public Network getNetworkPortGroupByName(VirtualMachine virtualMachine, String name) throws VSphereException {
        try {
            Datacenter datacenter = getDataCenter(virtualMachine);
            for (Network network : datacenter.getNetworks()) {
                if (network instanceof Network && (name.isEmpty() || network.getName().contentEquals(name))) {
                    return network;
                }
            }
        } catch (Exception e) {
            throw new VSphereException(e);
        }
        return null;
    }

    /**
     * Find Distributed Virtual Port Group name in the same Datacenter as the VM
     * @param virtualMachine - VM object
     * @param name - the name of the Port Group
     * @return returns DistributedVirtualPortgroup object for the provided vDS PortGroup
     * @throws VSphereException If an error occurred.
     */
    public DistributedVirtualPortgroup getDistributedVirtualPortGroupByName(VirtualMachine virtualMachine,
            String name) throws VSphereException {
        try {
            Datacenter datacenter = getDataCenter(virtualMachine);
            for (Network network : datacenter.getNetworks()) {
                if (network instanceof DistributedVirtualPortgroup
                        && (name.isEmpty() || network.getName().contentEquals(name))) {
                    return (DistributedVirtualPortgroup) network;
                }
            }
        } catch (Exception e) {
            throw new VSphereException(e);
        }
        return null;
    }

    /**
     * Find Distributed Virtual Switch from the provided Distributed Virtual Portgroup
     * @param distributedVirtualPortgroup - DistributedVirtualPortgroup object for the provided vDS PortGroup
     * @return returns DistributedVirtualSwitch object that represents the vDS Switch
     * @throws VSphereException If an error occurred.
     */
    public DistributedVirtualSwitch getDistributedVirtualSwitchByPortGroup(
            DistributedVirtualPortgroup distributedVirtualPortgroup) throws VSphereException {
        try {
            ManagedObjectReference managedObjectReference = new ManagedObjectReference();
            managedObjectReference.setType("DistributedVirtualSwitch");
            managedObjectReference
                    .setVal(distributedVirtualPortgroup.getConfig().getDistributedVirtualSwitch().getVal());
            return new DistributedVirtualSwitch(getServiceInstance().getServerConnection(), managedObjectReference);
        } catch (Exception e) {
            throw new VSphereException(e);
        }
    }

    /**
     * Passes data to a VM's "extra config" object. This data can then be read
     * back at a later stage.
     * In the case of parameters whose name starts "guestinfo.", the parameter
     * can be read by the VMware Tools on the client OS.
     * <p>
     * e.g. a variable named "guestinfo.Foo" with value "Bar" could be read on
     * the guest using the command-line
     * <tt>vmtoolsd --cmd "info-get guestinfo.Foo"</tt>.
     * </p>
     * 
     * @param vmName
     *            The name of the VM.
     * @param parameters
     *            A {@link Map} of variable name to variable value.
     * @throws VSphereException
     *             If an error occurred.
     */
    public void setExtraConfigParameters(String vmName, Map<String, String> parameters) throws VSphereException {
        VirtualMachineConfigSpec cs = createVMConfigSpecFromExtraConfigParameters(parameters);
        reconfigureVm(vmName, cs);
    }

    private static VirtualMachineConfigSpec createVMConfigSpecFromExtraConfigParameters(
            Map<String, String> parameters) {
        VirtualMachineConfigSpec cs = new VirtualMachineConfigSpec();
        OptionValue[] ourOptionValues = new OptionValue[parameters.size()];
        List<OptionValue> optionValues = new ArrayList<>();
        for (Map.Entry<String, String> eachVariable : parameters.entrySet()) {
            OptionValue ov = new OptionValue();
            ov.setKey(eachVariable.getKey());
            ov.setValue(eachVariable.getValue());
            optionValues.add(ov);
        }
        for (int i = 0; i < optionValues.size(); i++) {
            ourOptionValues[i] = optionValues.get(i);
        }
        cs.setExtraConfig(ourOptionValues);
        return cs;
    }

    private void logMessage(PrintStream jLogger, String message) {
        if (jLogger != null) {
            VSphereLogger.vsLogger(jLogger, message);
        }
        LOGGER.log(Level.FINER, message);
    }

    /**
     * Creates a {@link VSphereException} whose cause is the {@link TaskInfo}'s
     * exception. This provides an exception that is much more informative than
     * what is said by the <code>message</code> alone.
     * 
     * @param taskInfo
     *            The vSphere task that failed.
     * @param message
     *            A line of text that says what the task was trying to achieve.
     * @return An exception that includes the cause of the failure.
     */
    private static VSphereException newVSphereException(TaskInfo taskInfo, final String message) {
        final com.vmware.vim25.LocalizedMethodFault error = taskInfo == null ? null : taskInfo.getError();
        final String faultMsg = error == null ? null : error.getLocalizedMessage();
        final Exception fault = error == null ? null : error.getFault();
        final String combinedMsg = message + (faultMsg == null ? "" : ("\n" + faultMsg));
        if (fault != null) {
            return new VSphereException(combinedMsg, fault);
        } else {
            return new VSphereException(combinedMsg);
        }
    }
}